KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ofbiz > order > shoppingcart > product > ProductPromoWorker


1 /*
2  * $Id: ProductPromoWorker.java 7355 2006-04-20 21:32:54Z jonesde $
3  *
4  * Copyright (c) 2001-2005 The Open For Business Project - www.ofbiz.org
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
21  * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */

24 package org.ofbiz.order.shoppingcart.product;
25
26 import java.sql.Timestamp JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.util.Iterator JavaDoc;
31 import java.util.LinkedList JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Locale JavaDoc;
34 import java.util.Map JavaDoc;
35 import java.util.Set JavaDoc;
36
37 import javax.servlet.ServletRequest JavaDoc;
38 import javax.servlet.http.HttpServletRequest JavaDoc;
39
40 import javolution.util.FastList;
41
42 import org.ofbiz.base.util.Debug;
43 import org.ofbiz.base.util.GeneralException;
44 import org.ofbiz.base.util.UtilDateTime;
45 import org.ofbiz.base.util.UtilMisc;
46 import org.ofbiz.base.util.UtilProperties;
47 import org.ofbiz.base.util.UtilValidate;
48 import org.ofbiz.entity.GenericDelegator;
49 import org.ofbiz.entity.GenericEntityException;
50 import org.ofbiz.entity.GenericValue;
51 import org.ofbiz.entity.condition.EntityCondition;
52 import org.ofbiz.entity.condition.EntityConditionList;
53 import org.ofbiz.entity.condition.EntityExpr;
54 import org.ofbiz.entity.condition.EntityOperator;
55 import org.ofbiz.entity.util.EntityUtil;
56 import org.ofbiz.order.shoppingcart.CartItemModifyException;
57 import org.ofbiz.order.shoppingcart.ShoppingCart;
58 import org.ofbiz.order.shoppingcart.ShoppingCartEvents;
59 import org.ofbiz.order.shoppingcart.ShoppingCartItem;
60 import org.ofbiz.product.product.ProductContentWrapper;
61 import org.ofbiz.product.product.ProductSearch;
62 import org.ofbiz.service.GenericServiceException;
63 import org.ofbiz.service.LocalDispatcher;
64 import org.ofbiz.service.ModelService;
65 import org.ofbiz.service.ServiceUtil;
66
67 /**
68  * ProductPromoWorker - Worker class for catalog/product promotion related functionality
69  *
70  * @author <a HREF="mailto:jonesde@ofbiz.org">David E. Jones</a>
71  * @author <a HREF="mailto:jaz@ofbiz.org">Andy Zeneski</a>
72  * @version $Rev: 7355 $
73  * @since 2.0
74  */

75 public class ProductPromoWorker {
76
77     public static final String JavaDoc module = ProductPromoWorker.class.getName();
78     public static final String JavaDoc resource_error = "OrderErrorUiLabels";
79
80     public static List JavaDoc getStoreProductPromos(GenericDelegator delegator, LocalDispatcher dispatcher, ServletRequest JavaDoc request) {
81         List JavaDoc productPromos = FastList.newInstance();
82         Timestamp JavaDoc nowTimestamp = UtilDateTime.nowTimestamp();
83
84         // get the ShoppingCart out of the session.
85
HttpServletRequest JavaDoc req = null;
86         ShoppingCart cart = null;
87         try {
88             req = (HttpServletRequest JavaDoc) request;
89             cart = ShoppingCartEvents.getCartObject(req);
90         } catch (ClassCastException JavaDoc cce) {
91             Debug.logError("Not a HttpServletRequest, no shopping cart found.", module);
92             return null;
93         } catch (IllegalArgumentException JavaDoc e) {
94             Debug.logError(e, module);
95             return null;
96         }
97
98         boolean condResult = true;
99
100         try {
101             String JavaDoc productStoreId = cart.getProductStoreId();
102             GenericValue productStore = null;
103             try {
104                 productStore = delegator.findByPrimaryKeyCache("ProductStore", UtilMisc.toMap("productStoreId", productStoreId));
105             } catch (GenericEntityException e) {
106                 Debug.logError(e, "Error looking up store with id " + productStoreId, module);
107             }
108             if (productStore == null) {
109                 Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderNoStoreFoundWithIdNotDoingPromotions", UtilMisc.toMap("productStoreId",productStoreId), cart.getLocale()), module);
110                 return productPromos;
111             }
112
113             if (productStore != null) {
114                 Iterator JavaDoc productStorePromoAppls = UtilMisc.toIterator(EntityUtil.filterByDate(productStore.getRelatedCache("ProductStorePromoAppl", UtilMisc.toMap("productStoreId", productStoreId), UtilMisc.toList("sequenceNum")), true));
115                 while (productStorePromoAppls != null && productStorePromoAppls.hasNext()) {
116                     GenericValue productStorePromoAppl = (GenericValue) productStorePromoAppls.next();
117                     GenericValue productPromo = productStorePromoAppl.getRelatedOneCache("ProductPromo");
118                     List JavaDoc productPromoRules = productPromo.getRelatedCache("ProductPromoRule", null, null);
119
120
121                     if (productPromoRules != null) {
122                         Iterator JavaDoc promoRulesItr = productPromoRules.iterator();
123
124                         while (condResult && promoRulesItr != null && promoRulesItr.hasNext()) {
125                             GenericValue promoRule = (GenericValue) promoRulesItr.next();
126                             Iterator JavaDoc productPromoConds = UtilMisc.toIterator(promoRule.getRelatedCache("ProductPromoCond", null, UtilMisc.toList("productPromoCondSeqId")));
127
128                             while (condResult && productPromoConds != null && productPromoConds.hasNext()) {
129                                 GenericValue productPromoCond = (GenericValue) productPromoConds.next();
130
131                                 // evaluate the party related conditions; so we don't show the promo if it doesn't apply.
132
if ("PPIP_PARTY_ID".equals(productPromoCond.getString("inputParamEnumId"))) {
133                                     condResult = checkCondition(productPromoCond, cart, delegator, dispatcher, nowTimestamp);
134                                 } else if ("PPIP_PARTY_GRP_MEM".equals(productPromoCond.getString("inputParamEnumId"))) {
135                                     condResult = checkCondition(productPromoCond, cart, delegator, dispatcher, nowTimestamp);
136                                 } else if ("PPIP_PARTY_CLASS".equals(productPromoCond.getString("inputParamEnumId"))) {
137                                     condResult = checkCondition(productPromoCond, cart, delegator, dispatcher, nowTimestamp);
138                                 } else if ("PPIP_ROLE_TYPE".equals(productPromoCond.getString("inputParamEnumId"))) {
139                                     condResult = checkCondition(productPromoCond, cart, delegator, dispatcher, nowTimestamp);
140                                 }
141                             }
142                         }
143                         if (!condResult) productPromo = null;
144                     }
145                     if (productPromo != null) productPromos.add(productPromo);
146                 }
147             }
148         } catch (GenericEntityException e) {
149             Debug.logError(e, module);
150         }
151         return productPromos;
152     }
153
154     public static List JavaDoc getProductStorePromotions(ShoppingCart cart, Timestamp JavaDoc nowTimestamp, LocalDispatcher dispatcher) {
155         List JavaDoc productPromoList = FastList.newInstance();
156         
157         GenericDelegator delegator = cart.getDelegator();
158
159         String JavaDoc productStoreId = cart.getProductStoreId();
160         GenericValue productStore = null;
161         try {
162             productStore = delegator.findByPrimaryKeyCache("ProductStore", UtilMisc.toMap("productStoreId", productStoreId));
163         } catch (GenericEntityException e) {
164             Debug.logError(e, "Error looking up store with id " + productStoreId, module);
165         }
166         if (productStore == null) {
167             Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderNoStoreFoundWithIdNotDoingPromotions", UtilMisc.toMap("productStoreId",productStoreId), cart.getLocale()), module);
168             return productPromoList;
169         }
170
171         try {
172             // loop through promotions and get a list of all of the rules...
173
List JavaDoc productStorePromoApplsList = productStore.getRelatedCache("ProductStorePromoAppl", null, UtilMisc.toList("sequenceNum"));
174             productStorePromoApplsList = EntityUtil.filterByDate(productStorePromoApplsList, nowTimestamp);
175
176             if (productStorePromoApplsList == null || productStorePromoApplsList.size() == 0) {
177                 if (Debug.verboseOn()) Debug.logVerbose("Not doing promotions, none applied to store with ID " + productStoreId, module);
178             }
179
180             Iterator JavaDoc prodCatalogPromoAppls = UtilMisc.toIterator(productStorePromoApplsList);
181             while (prodCatalogPromoAppls != null && prodCatalogPromoAppls.hasNext()) {
182                 GenericValue prodCatalogPromoAppl = (GenericValue) prodCatalogPromoAppls.next();
183                 GenericValue productPromo = prodCatalogPromoAppl.getRelatedOneCache("ProductPromo");
184                 productPromoList.add(productPromo);
185             }
186         } catch (GenericEntityException e) {
187             Debug.logError(e, "Error looking up promotion data while doing promotions", module);
188         }
189         return productPromoList;
190     }
191
192     public static List JavaDoc getAgreementPromotions(ShoppingCart cart, Timestamp JavaDoc nowTimestamp, LocalDispatcher dispatcher) {
193         List JavaDoc productPromoList = FastList.newInstance();
194         
195         GenericDelegator delegator = cart.getDelegator();
196
197         String JavaDoc agreementId = cart.getAgreementId();
198         GenericValue agreement = null;
199         try {
200             agreement = delegator.findByPrimaryKeyCache("Agreement", UtilMisc.toMap("agreementId", agreementId));
201         } catch (GenericEntityException e) {
202             Debug.logError(e, "Error looking up agreement with id " + agreementId, module);
203         }
204         if (agreement == null) {
205             Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderNoAgreementFoundWithIdNotDoingPromotions", UtilMisc.toMap("agreementId", agreementId), cart.getLocale()), module);
206             return productPromoList;
207         }
208         GenericValue agreementItem = null;
209         try {
210             List JavaDoc agreementItems = delegator.findByAndCache("AgreementItem", UtilMisc.toMap("agreementId", agreementId, "agreementItemTypeId", "AGREEMENT_PRICING_PR", "currencyUomId", cart.getCurrency()));
211             agreementItem = EntityUtil.getFirst(agreementItems);
212         } catch (GenericEntityException e) {
213             Debug.logError(e, "Error looking up agreement items for agreement with id " + agreementId, module);
214         }
215         if (agreementItem == null) {
216             Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderNoAgreementItemFoundForAgreementWithIdNotDoingPromotions", UtilMisc.toMap("agreementId", agreementId), cart.getLocale()), module);
217             return productPromoList;
218         }
219
220         try {
221             // loop through promotions and get a list of all of the rules...
222
List JavaDoc agreementPromoApplsList = agreementItem.getRelatedCache("AgreementPromoAppl", null, UtilMisc.toList("sequenceNum"));
223             agreementPromoApplsList = EntityUtil.filterByDate(agreementPromoApplsList, nowTimestamp);
224
225             if (agreementPromoApplsList == null || agreementPromoApplsList.size() == 0) {
226                 if (Debug.verboseOn()) Debug.logVerbose("Not doing promotions, none applied to agreement with ID " + agreementId, module);
227             }
228
229             Iterator JavaDoc agreementPromoAppls = UtilMisc.toIterator(agreementPromoApplsList);
230             while (agreementPromoAppls != null && agreementPromoAppls.hasNext()) {
231                 GenericValue agreementPromoAppl = (GenericValue) agreementPromoAppls.next();
232                 GenericValue productPromo = agreementPromoAppl.getRelatedOneCache("ProductPromo");
233                 productPromoList.add(productPromo);
234             }
235         } catch (GenericEntityException e) {
236             Debug.logError(e, "Error looking up promotion data while doing promotions", module);
237         }
238         return productPromoList;
239     }
240
241     public static void doPromotions(ShoppingCart cart, LocalDispatcher dispatcher) {
242         ProductPromoWorker.doPromotions(cart, null, dispatcher);
243     }
244
245     public static void doPromotions(ShoppingCart cart, List JavaDoc productPromoList, LocalDispatcher dispatcher) {
246         // this is called when a user logs in so that per customer limits are honored, called by cart when new userlogin is set
247
// there is code to store ProductPromoUse information when an order is placed
248
// ProductPromoUses are ignored if the corresponding order is cancelled
249
// limits sub total for promos to not use gift cards (products with a don't use in promo indicator), also exclude gift cards from all other promotion considerations including subTotals for discounts, etc
250
// TODO: (not done, delay, still considering...) add code to check ProductPromoUse limits per promo (customer, promo), and per code (customer, code) to avoid use of promos or codes getting through due to multiple carts getting promos applied at the same time, possibly on totally different servers
251

252         GenericDelegator delegator = cart.getDelegator();
253         Timestamp JavaDoc nowTimestamp = UtilDateTime.nowTimestamp();
254
255         // start out by clearing all existing promotions, then we can just add all that apply
256
cart.clearAllPromotionInformation();
257         
258         // there will be a ton of db access, so just do a big catch entity exception block
259
try {
260             if (productPromoList == null) {
261                 if (cart.getOrderType().equals("SALES_ORDER")) {
262                     productPromoList = ProductPromoWorker.getProductStorePromotions(cart, nowTimestamp, dispatcher);
263                 } else {
264                     productPromoList = ProductPromoWorker.getAgreementPromotions(cart, nowTimestamp, dispatcher);
265                 }
266             }
267             // do a calculate only run through the promotions, then order by descending totalDiscountAmount for each promotion
268
// NOTE: on this run, with isolatedTestRun passed as false it should not apply any adjustments
269
// or track which cart items are used for which promotions, but it will track ProductPromoUseInfo and
270
// useLimits; we are basicly just trying to run each promo "independently" to see how much each is worth
271
runProductPromos(productPromoList, cart, delegator, dispatcher, nowTimestamp, true);
272             
273             // 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
274
// 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
275
// create a List of Maps with productPromo and totalDiscountAmount, use the Map sorter to sort them descending by totalDiscountAmount
276

277             // 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
278
List JavaDoc productPromoDiscountMapList = FastList.newInstance();
279             List JavaDoc productPromoDiscountMapListOrderTotal = FastList.newInstance();
280             Iterator JavaDoc productPromoIter = productPromoList.iterator();
281             while (productPromoIter.hasNext()) {
282                 GenericValue productPromo = (GenericValue) productPromoIter.next();
283                 Map JavaDoc productPromoDiscountMap = UtilMisc.toMap("productPromo", productPromo, "totalDiscountAmount", new Double JavaDoc(cart.getProductPromoUseTotalDiscount(productPromo.getString("productPromoId"))));
284                 if (hasOrderTotalCondition(productPromo, delegator)) {
285                     productPromoDiscountMapListOrderTotal.add(productPromoDiscountMap);
286                 } else {
287                     productPromoDiscountMapList.add(productPromoDiscountMap);
288                 }
289             }
290             
291             
292             // sort the Map List, do it ascending because the discount amounts will be negative, so the lowest number is really the highest discount
293
productPromoDiscountMapList = UtilMisc.sortMaps(productPromoDiscountMapList, UtilMisc.toList("+totalDiscountAmount"));
294             productPromoDiscountMapListOrderTotal = UtilMisc.sortMaps(productPromoDiscountMapListOrderTotal, UtilMisc.toList("+totalDiscountAmount"));
295
296             productPromoDiscountMapList.addAll(productPromoDiscountMapListOrderTotal);
297             
298             List JavaDoc sortedProductPromoList = new ArrayList JavaDoc(productPromoDiscountMapList.size());
299             Iterator JavaDoc productPromoDiscountMapIter = productPromoDiscountMapList.iterator();
300             while (productPromoDiscountMapIter.hasNext()) {
301                 Map JavaDoc productPromoDiscountMap = (Map JavaDoc) productPromoDiscountMapIter.next();
302                 GenericValue productPromo = (GenericValue) productPromoDiscountMap.get("productPromo");
303                 sortedProductPromoList.add(productPromo);
304                 if (Debug.verboseOn()) Debug.logVerbose("Sorted Promo [" + productPromo.getString("productPromoId") + "] with total discount: " + productPromoDiscountMap.get("totalDiscountAmount"), module);
305             }
306             
307             // okay, all ready, do the real run, clearing the temporary result first...
308
cart.clearAllPromotionInformation();
309             runProductPromos(sortedProductPromoList, cart, delegator, dispatcher, nowTimestamp, false);
310         } catch (NumberFormatException JavaDoc e) {
311             Debug.logError(e, "Number not formatted correctly in promotion rules, not completed...", module);
312         } catch (GenericEntityException e) {
313             Debug.logError(e, "Error looking up promotion data while doing promotions", module);
314         } catch (Exception JavaDoc e) {
315             Debug.logError(e, "Error running promotions, will ignore: " + e.toString(), module);
316         }
317     }
318
319     protected static boolean hasOrderTotalCondition(GenericValue productPromo, GenericDelegator delegator) throws GenericEntityException {
320         boolean hasOtCond = false;
321         List JavaDoc productPromoConds = delegator.findByAndCache("ProductPromoCond", UtilMisc.toMap("productPromoId", productPromo.get("productPromoId")), UtilMisc.toList("productPromoCondSeqId"));
322         Iterator JavaDoc productPromoCondIter = productPromoConds.iterator();
323         while (productPromoCondIter.hasNext()) {
324             GenericValue productPromoCond = (GenericValue) productPromoCondIter.next();
325             String JavaDoc inputParamEnumId = productPromoCond.getString("inputParamEnumId");
326             if ("PPIP_ORDER_TOTAL".equals(inputParamEnumId)) {
327                 hasOtCond = true;
328                 break;
329             }
330         }
331         return hasOtCond;
332     }
333     
334     protected static void runProductPromos(List JavaDoc productPromoList, ShoppingCart cart, GenericDelegator delegator, LocalDispatcher dispatcher, Timestamp JavaDoc nowTimestamp, boolean isolatedTestRun) throws GeneralException {
335         String JavaDoc partyId = cart.getPartyId();
336
337         // this is our safety net; we should never need to loop through the rules more than a certain number of times, this is that number and may have to be changed for insanely large promo sets...
338
long maxIterations = 1000;
339         // part of the safety net to avoid infinite iteration
340
long numberOfIterations = 0;
341         
342         // set a max limit on how many times each promo can be run, for cases where there is no use limit this will be the use limit
343
//default to 2 times the number of items in the cart
344
long maxUseLimit = 2 * Math.round(cart.getTotalQuantity());
345         
346         try {
347             // repeat until no more rules to run: either all rules are run, or no changes to the cart in a loop
348
boolean cartChanged = true;
349             while (cartChanged) {
350                 cartChanged = false;
351                 numberOfIterations++;
352                 if (numberOfIterations > maxIterations) {
353                     Debug.logError("ERROR: While calculating promotions the promotion rules where run more than " + maxIterations + " times, so the calculation has been ended. This should generally never happen unless you have bad rule definitions.", module);
354                     break;
355                 }
356
357                 Iterator JavaDoc productPromoIter = productPromoList.iterator();
358                 while (productPromoIter.hasNext()) {
359                     GenericValue productPromo = (GenericValue) productPromoIter.next();
360                     String JavaDoc productPromoId = productPromo.getString("productPromoId");
361
362                     List JavaDoc productPromoRules = productPromo.getRelatedCache("ProductPromoRule", null, null);
363                     if (productPromoRules != null && productPromoRules.size() > 0) {
364                         // always have a useLimit to avoid unlimited looping, default to 1 if no other is specified
365
Long JavaDoc candidateUseLimit = getProductPromoUseLimit(productPromo, partyId, delegator);
366                         Long JavaDoc useLimit = candidateUseLimit;
367                         if (Debug.verboseOn()) Debug.logVerbose("Running promotion [" + productPromoId + "], useLimit=" + useLimit + ", # of rules=" + productPromoRules.size(), module);
368
369                         boolean requireCode = "Y".equals(productPromo.getString("requireCode"));
370                         // check if promo code required
371
if (requireCode) {
372                             Set JavaDoc enteredCodes = cart.getProductPromoCodesEntered();
373                             if (enteredCodes.size() > 0) {
374                                 // get all promo codes entered, do a query with an IN condition to see if any of those are related
375
EntityCondition codeCondition = new EntityExpr(new EntityExpr("productPromoId", EntityOperator.EQUALS, productPromoId), EntityOperator.AND, new EntityExpr("productPromoCodeId", EntityOperator.IN, enteredCodes));
376                                 // may want to sort by something else to decide which code to use if there is more than one candidate
377
List JavaDoc productPromoCodeList = delegator.findByCondition("ProductPromoCode", codeCondition, null, UtilMisc.toList("productPromoCodeId"));
378                                 Iterator JavaDoc productPromoCodeIter = productPromoCodeList.iterator();
379                                 // support multiple promo codes for a single promo, ie if we run into a use limit for one code see if we can find another for this promo
380
// check the use limit before each pass so if the promo use limit has been hit we don't keep on trying for the promo code use limit, if there is one of course
381
while ((useLimit == null || useLimit.longValue() > cart.getProductPromoUseCount(productPromoId)) && productPromoCodeIter.hasNext()) {
382                                     GenericValue productPromoCode = (GenericValue) productPromoCodeIter.next();
383                                     String JavaDoc productPromoCodeId = productPromoCode.getString("productPromoCodeId");
384                                     Long JavaDoc codeUseLimit = getProductPromoCodeUseLimit(productPromoCode, partyId, delegator);
385                                     if (runProductPromoRules(cart, cartChanged, useLimit, true, productPromoCodeId, codeUseLimit, maxUseLimit, productPromo, productPromoRules, dispatcher, delegator, nowTimestamp)) {
386                                         cartChanged = true;
387                                     }
388                                     
389                                     if (cart.getProductPromoUseCount(productPromoId) > maxUseLimit) {
390                                         Debug.logError("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.", module);
391                                         break;
392                                     }
393                                 }
394                             }
395                         } else {
396                             try {
397                                 if (runProductPromoRules(cart, cartChanged, useLimit, false, null, null, maxUseLimit, productPromo, productPromoRules, dispatcher, delegator, nowTimestamp)) {
398                                     cartChanged = true;
399                                 }
400                             } catch (RuntimeException JavaDoc e) {
401                                 throw new GeneralException("Error running promotion with ID [" + productPromoId + "]", e);
402                             }
403                         }
404                     }
405                     
406                     // if this is an isolatedTestRun clear out adjustments and cart item promo use info
407
if (isolatedTestRun) {
408                         cart.clearAllPromotionAdjustments();
409                         cart.clearCartItemUseInPromoInfo();
410                     }
411                 }
412                 
413                 // if this is an isolatedTestRun, then only go through it once, never retry
414
if (isolatedTestRun) {
415                     cartChanged = false;
416                 }
417             }
418         } catch (UseLimitException e) {
419             Debug.logError(e, e.toString(), module);
420         }
421     }
422
423     /** calculate low use limit for this promo for the current "order", check per order, customer, promo */
424     public static Long JavaDoc getProductPromoUseLimit(GenericValue productPromo, String JavaDoc partyId, GenericDelegator delegator) throws GenericEntityException {
425         String JavaDoc productPromoId = productPromo.getString("productPromoId");
426         Long JavaDoc candidateUseLimit = null;
427
428         Long JavaDoc useLimitPerOrder = productPromo.getLong("useLimitPerOrder");
429         if (useLimitPerOrder != null) {
430             if (candidateUseLimit == null || candidateUseLimit.longValue() > useLimitPerOrder.longValue()) {
431                 candidateUseLimit = useLimitPerOrder;
432             }
433         }
434         
435         // Debug.logInfo("Promo [" + productPromoId + "] use limit after per order check: " + candidateUseLimit, module);
436

437         Long JavaDoc useLimitPerCustomer = productPromo.getLong("useLimitPerCustomer");
438         // check this whether or not there is a party right now
439
if (useLimitPerCustomer != null) {
440             // if partyId is not empty check previous usage
441
long productPromoCustomerUseSize = 0;
442             if (UtilValidate.isNotEmpty(partyId)) {
443                 // check to see how many times this has been used for other orders for this customer, the remainder is the limit for this order
444
EntityCondition checkCondition = new EntityConditionList(UtilMisc.toList(
445                         new EntityExpr("productPromoId", EntityOperator.EQUALS, productPromoId),
446                         new EntityExpr("partyId", EntityOperator.EQUALS, partyId),
447                         new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_REJECTED"),
448                         new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_CANCELLED")), EntityOperator.AND);
449                 productPromoCustomerUseSize = delegator.findCountByCondition("ProductPromoUseCheck", checkCondition, null);
450             }
451             long perCustomerThisOrder = useLimitPerCustomer.longValue() - productPromoCustomerUseSize;
452             if (candidateUseLimit == null || candidateUseLimit.longValue() > perCustomerThisOrder) {
453                 candidateUseLimit = new Long JavaDoc(perCustomerThisOrder);
454             }
455         }
456
457         // Debug.logInfo("Promo [" + productPromoId + "] use limit after per customer check: " + candidateUseLimit, module);
458

459         Long JavaDoc useLimitPerPromotion = productPromo.getLong("useLimitPerPromotion");
460         if (useLimitPerPromotion != null) {
461             // check to see how many times this has been used for other orders for this customer, the remainder is the limit for this order
462
EntityCondition checkCondition = new EntityConditionList(UtilMisc.toList(
463                     new EntityExpr("productPromoId", EntityOperator.EQUALS, productPromoId),
464                     new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_REJECTED"),
465                     new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_CANCELLED")), EntityOperator.AND);
466             long productPromoUseSize = delegator.findCountByCondition("ProductPromoUseCheck", checkCondition, null);
467             long perPromotionThisOrder = useLimitPerPromotion.longValue() - productPromoUseSize;
468             if (candidateUseLimit == null || candidateUseLimit.longValue() > perPromotionThisOrder) {
469                 candidateUseLimit = new Long JavaDoc(perPromotionThisOrder);
470             }
471         }
472
473         // Debug.logInfo("Promo [" + productPromoId + "] use limit after per promotion check: " + candidateUseLimit, module);
474

475         return candidateUseLimit;
476     }
477
478     public static Long JavaDoc getProductPromoCodeUseLimit(GenericValue productPromoCode, String JavaDoc partyId, GenericDelegator delegator) throws GenericEntityException {
479         String JavaDoc productPromoCodeId = productPromoCode.getString("productPromoCodeId");
480         Long JavaDoc codeUseLimit = null;
481
482         // check promo code use limits, per customer, code
483
Long JavaDoc codeUseLimitPerCustomer = productPromoCode.getLong("useLimitPerCustomer");
484         if (codeUseLimitPerCustomer != null && UtilValidate.isNotEmpty(partyId)) {
485             // check to see how many times this has been used for other orders for this customer, the remainder is the limit for this order
486
EntityCondition checkCondition = new EntityConditionList(UtilMisc.toList(
487                     new EntityExpr("productPromoCodeId", EntityOperator.EQUALS, productPromoCodeId),
488                     new EntityExpr("partyId", EntityOperator.EQUALS, partyId),
489                     new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_REJECTED"),
490                     new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_CANCELLED")), EntityOperator.AND);
491             long productPromoCustomerUseSize = delegator.findCountByCondition("ProductPromoUseCheck", checkCondition, null);
492             long perCustomerThisOrder = codeUseLimitPerCustomer.longValue() - productPromoCustomerUseSize;
493             if (codeUseLimit == null || codeUseLimit.longValue() > perCustomerThisOrder) {
494                 codeUseLimit = new Long JavaDoc(perCustomerThisOrder);
495             }
496         }
497
498         Long JavaDoc codeUseLimitPerCode = productPromoCode.getLong("useLimitPerCode");
499         if (codeUseLimitPerCode != null) {
500             // check to see how many times this has been used for other orders for this customer, the remainder is the limit for this order
501
EntityCondition checkCondition = new EntityConditionList(UtilMisc.toList(
502                     new EntityExpr("productPromoCodeId", EntityOperator.EQUALS, productPromoCodeId),
503                     new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_REJECTED"),
504                     new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "ORDER_CANCELLED")), EntityOperator.AND);
505             long productPromoCodeUseSize = delegator.findCountByCondition("ProductPromoUseCheck", checkCondition, null);
506             long perCodeThisOrder = codeUseLimitPerCode.longValue() - productPromoCodeUseSize;
507             if (codeUseLimit == null || codeUseLimit.longValue() > perCodeThisOrder) {
508                 codeUseLimit = new Long JavaDoc(perCodeThisOrder);
509             }
510         }
511
512         return codeUseLimit;
513     }
514     
515     public static String JavaDoc checkCanUsePromoCode(String JavaDoc productPromoCodeId, String JavaDoc partyId, GenericDelegator delegator) {
516         try {
517             GenericValue productPromoCode = delegator.findByPrimaryKey("ProductPromoCode", UtilMisc.toMap("productPromoCodeId", productPromoCodeId));
518             if (productPromoCode == null) {
519                 return "The promotion code [" + productPromoCodeId + "] is not valid.";
520             }
521             
522             if ("Y".equals(productPromoCode.getString("requireEmailOrParty"))) {
523                 boolean hasEmailOrParty = false;
524                 
525                 // check partyId
526
if (UtilValidate.isNotEmpty(partyId)) {
527                     if (delegator.findByPrimaryKey("ProductPromoCodeParty", UtilMisc.toMap("productPromoCodeId", productPromoCodeId, "partyId", partyId)) != null) {
528                         // found party associated with the code, looks good...
529
return null;
530                     }
531                     
532                     // check email address in ProductPromoCodeEmail
533
Timestamp JavaDoc nowTimestamp = UtilDateTime.nowTimestamp();
534                     List JavaDoc validEmailCondList = new ArrayList JavaDoc();
535                     validEmailCondList.add(new EntityExpr("partyId", EntityOperator.EQUALS, partyId));
536                     validEmailCondList.add(new EntityExpr("productPromoCodeId", EntityOperator.EQUALS, productPromoCodeId));
537                     validEmailCondList.add(new EntityExpr("fromDate", EntityOperator.LESS_THAN_EQUAL_TO, nowTimestamp));
538                     validEmailCondList.add(new EntityExpr(new EntityExpr("thruDate", EntityOperator.GREATER_THAN_EQUAL_TO, nowTimestamp),
539                             EntityOperator.OR, new EntityExpr("thruDate", EntityOperator.EQUALS, null)));
540                     EntityCondition validEmailCondition = new EntityConditionList(validEmailCondList, EntityOperator.AND);
541                     long validEmailCount = delegator.findCountByCondition("ProductPromoCodeEmailParty", validEmailCondition, null);
542                     if (validEmailCount > 0) {
543                         // there was an email in the list, looks good...
544
return null;
545                     }
546                 }
547                 
548                 if (!hasEmailOrParty) {
549                     return "This promotion code [" + productPromoCodeId + "] requires you to be associated with it by account or email address and you are not associated with it.";
550                 }
551             }
552             
553             // check per customer and per promotion code use limits
554
Long JavaDoc useLimit = getProductPromoCodeUseLimit(productPromoCode, partyId, delegator);
555             if (useLimit != null && useLimit.longValue() <= 0) {
556                 return "This promotion code [" + productPromoCodeId + "] has reached it's maximum use limit for you and can no longer be used.";
557             }
558             
559             return null;
560         } catch (GenericEntityException e) {
561             Debug.logError(e, "Error looking up ProductPromoCode", module);
562             return "Error looking up code [" + productPromoCodeId + "]:" + e.toString();
563         }
564     }
565
566     public static String JavaDoc makeAutoDescription(GenericValue productPromo, GenericDelegator delegator, Locale JavaDoc locale) throws GenericEntityException {
567         if (productPromo == null) {
568             return "";
569         }
570         StringBuffer JavaDoc promoDescBuf = new StringBuffer JavaDoc();
571         List JavaDoc productPromoRules = productPromo.getRelatedCache("ProductPromoRule", null, null);
572         Iterator JavaDoc promoRulesIter = productPromoRules.iterator();
573         while (promoRulesIter != null && promoRulesIter.hasNext()) {
574             GenericValue productPromoRule = (GenericValue) promoRulesIter.next();
575
576             List JavaDoc productPromoConds = delegator.findByAndCache("ProductPromoCond", UtilMisc.toMap("productPromoId", productPromo.get("productPromoId")), UtilMisc.toList("productPromoCondSeqId"));
577             productPromoConds = EntityUtil.filterByAnd(productPromoConds, UtilMisc.toMap("productPromoRuleId", productPromoRule.get("productPromoRuleId")));
578             // using the other method to consolodate cache entries because the same cache is used elsewhere: List productPromoConds = productPromoRule.getRelatedCache("ProductPromoCond", null, UtilMisc.toList("productPromoCondSeqId"));
579
Iterator JavaDoc productPromoCondIter = UtilMisc.toIterator(productPromoConds);
580             while (productPromoCondIter != null && productPromoCondIter.hasNext()) {
581                 GenericValue productPromoCond = (GenericValue) productPromoCondIter.next();
582
583                 String JavaDoc equalityOperator = UtilProperties.getMessage("promotext", "operator.equality." + productPromoCond.getString("operatorEnumId"), locale);
584                 String JavaDoc quantityOperator = UtilProperties.getMessage("promotext", "operator.quantity." + productPromoCond.getString("operatorEnumId"), locale);
585
586                 String JavaDoc condValue = "invalid";
587                 if (UtilValidate.isNotEmpty(productPromoCond.getString("condValue"))) {
588                     condValue = productPromoCond.getString("condValue");
589                 }
590                 
591                 Map JavaDoc messageContext = UtilMisc.toMap("condValue", condValue, "equalityOperator", equalityOperator, "quantityOperator", quantityOperator);
592                 String JavaDoc msgProp = UtilProperties.getMessage("promotext", "condition." + productPromoCond.getString("inputParamEnumId"), messageContext, locale);
593                 promoDescBuf.append(msgProp);
594                 promoDescBuf.append(" ");
595                 
596                 if (promoRulesIter.hasNext()) {
597                     promoDescBuf.append(" and ");
598                 }
599             }
600
601             List JavaDoc productPromoActions = productPromoRule.getRelatedCache("ProductPromoAction", null, UtilMisc.toList("productPromoActionSeqId"));
602             Iterator JavaDoc productPromoActionIter = UtilMisc.toIterator(productPromoActions);
603             while (productPromoActionIter != null && productPromoActionIter.hasNext()) {
604                 GenericValue productPromoAction = (GenericValue) productPromoActionIter.next();
605
606                 String JavaDoc productId = productPromoAction.getString("productId");
607                 
608                 Map JavaDoc messageContext = UtilMisc.toMap("quantity", productPromoAction.get("quantity"), "amount", productPromoAction.get("amount"), "productId", productId, "partyId", productPromoAction.get("partyId"));
609
610                 if (UtilValidate.isEmpty((String JavaDoc) messageContext.get("productId"))) messageContext.put("productId", "any");
611                 if (UtilValidate.isEmpty((String JavaDoc) messageContext.get("partyId"))) messageContext.put("partyId", "any");
612                 GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
613                 if (product != null) {
614                     messageContext.put("productName", ProductContentWrapper.getProductContentAsText(product, "PRODUCT_NAME", locale));
615                 }
616                 
617                 String JavaDoc msgProp = UtilProperties.getMessage("promotext", "action." + productPromoAction.getString("productPromoActionEnumId"), messageContext, locale);
618                 promoDescBuf.append(msgProp);
619                 promoDescBuf.append(" ");
620                 
621                 if (promoRulesIter.hasNext()) {
622                     promoDescBuf.append(" and ");
623                 }
624             }
625             
626             if (promoRulesIter.hasNext()) {
627                 promoDescBuf.append(" or ");
628             }
629         }
630
631         if (promoDescBuf.length() > 0) {
632             // remove any trailing space
633
if (promoDescBuf.charAt(promoDescBuf.length() - 1) == ' ') promoDescBuf.deleteCharAt(promoDescBuf.length() - 1);
634             // add a period
635
promoDescBuf.append(". ");
636             // capitalize the first letter
637
promoDescBuf.setCharAt(0, Character.toUpperCase(promoDescBuf.charAt(0)));
638         }
639         
640         if ("Y".equals(productPromo.getString("requireCode"))) {
641             promoDescBuf.append("Requires code to use. ");
642         }
643         if (productPromo.getLong("useLimitPerOrder") != null) {
644             promoDescBuf.append("Limit ");
645             promoDescBuf.append(productPromo.getLong("useLimitPerOrder"));
646             promoDescBuf.append(" per order. ");
647         }
648         if (productPromo.getLong("useLimitPerCustomer") != null) {
649             promoDescBuf.append("Limit ");
650             promoDescBuf.append(productPromo.getLong("useLimitPerCustomer"));
651             promoDescBuf.append(" per customer. ");
652         }
653         if (productPromo.getLong("useLimitPerPromotion") != null) {
654             promoDescBuf.append("Limit ");
655             promoDescBuf.append(productPromo.getLong("useLimitPerPromotion"));
656             promoDescBuf.append(" per promotion. ");
657         }
658         
659         return promoDescBuf.toString();
660     }
661     
662     protected static boolean runProductPromoRules(ShoppingCart cart, boolean cartChanged, Long JavaDoc useLimit, boolean requireCode, String JavaDoc productPromoCodeId, Long JavaDoc codeUseLimit, long maxUseLimit,
663             GenericValue productPromo, List JavaDoc productPromoRules, LocalDispatcher dispatcher, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp) throws GenericEntityException, UseLimitException {
664         String JavaDoc productPromoId = productPromo.getString("productPromoId");
665         while ((useLimit == null || useLimit.longValue() > cart.getProductPromoUseCount(productPromoId)) &&
666                 (!requireCode || UtilValidate.isNotEmpty(productPromoCodeId)) &&
667                 (codeUseLimit == null || codeUseLimit.longValue() > cart.getProductPromoCodeUse(productPromoCodeId))) {
668             boolean promoUsed = false;
669             double totalDiscountAmount = 0;
670             double quantityLeftInActions = 0;
671
672             Iterator JavaDoc promoRulesIter = productPromoRules.iterator();
673             while (promoRulesIter != null && promoRulesIter.hasNext()) {
674                 GenericValue productPromoRule = (GenericValue) promoRulesIter.next();
675
676                 // if apply then performActions when no conditions are false, so default to true
677
boolean performActions = true;
678
679                 // loop through conditions for rule, if any false, set allConditionsTrue to false
680
List JavaDoc productPromoConds = delegator.findByAndCache("ProductPromoCond", UtilMisc.toMap("productPromoId", productPromo.get("productPromoId")), UtilMisc.toList("productPromoCondSeqId"));
681                 productPromoConds = EntityUtil.filterByAnd(productPromoConds, UtilMisc.toMap("productPromoRuleId", productPromoRule.get("productPromoRuleId")));
682                 // using the other method to consolodate cache entries because the same cache is used elsewhere: List productPromoConds = productPromoRule.getRelatedCache("ProductPromoCond", null, UtilMisc.toList("productPromoCondSeqId"));
683
if (Debug.verboseOn()) Debug.logVerbose("Checking " + productPromoConds.size() + " conditions for rule " + productPromoRule, module);
684
685                 Iterator JavaDoc productPromoCondIter = UtilMisc.toIterator(productPromoConds);
686                 while (productPromoCondIter != null && productPromoCondIter.hasNext()) {
687                     GenericValue productPromoCond = (GenericValue) productPromoCondIter.next();
688
689                     boolean condResult = checkCondition(productPromoCond, cart, delegator, dispatcher, nowTimestamp);
690
691                     // any false condition will cause it to NOT perform the action
692
if (condResult == false) {
693                         performActions = false;
694                         break;
695                     }
696                 }
697
698                 if (performActions) {
699                     // perform all actions, either apply or unapply
700

701                     List JavaDoc productPromoActions = productPromoRule.getRelatedCache("ProductPromoAction", null, UtilMisc.toList("productPromoActionSeqId"));
702                     if (Debug.verboseOn()) Debug.logVerbose("Performing " + productPromoActions.size() + " actions for rule " + productPromoRule, module);
703                     Iterator JavaDoc productPromoActionIter = UtilMisc.toIterator(productPromoActions);
704                     while (productPromoActionIter != null && productPromoActionIter.hasNext()) {
705                         GenericValue productPromoAction = (GenericValue) productPromoActionIter.next();
706
707                         // Debug.logInfo("Doing action: " + productPromoAction, module);
708
try {
709                             ActionResultInfo actionResultInfo = performAction(productPromoAction, cart, delegator, dispatcher, nowTimestamp);
710                             totalDiscountAmount += actionResultInfo.totalDiscountAmount;
711                             quantityLeftInActions += actionResultInfo.quantityLeftInAction;
712                             
713                             // only set if true, don't set back to false: implements OR logic (ie if ANY actions change content, redo loop)
714
boolean actionChangedCart = actionResultInfo.ranAction;
715                             if (actionChangedCart) {
716                                 promoUsed = true;
717                                 cartChanged = true;
718                             }
719                         } catch (CartItemModifyException e) {
720                             Debug.logError("Error modifying the cart while performing promotion action [" + productPromoAction.getPrimaryKey() + "]: " + e.toString(), module);
721                         }
722                     }
723                 }
724             }
725
726             if (promoUsed) {
727                 cart.addProductPromoUse(productPromo.getString("productPromoId"), productPromoCodeId, totalDiscountAmount, quantityLeftInActions);
728             } else {
729                 // 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
730
break;
731             }
732
733             
734             if (cart.getProductPromoUseCount(productPromoId) > maxUseLimit) {
735                 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.");
736             }
737         }
738
739         return cartChanged;
740     }
741
742     protected static boolean checkCondition(GenericValue productPromoCond, ShoppingCart cart, GenericDelegator delegator, LocalDispatcher dispatcher, Timestamp JavaDoc nowTimestamp) throws GenericEntityException {
743         String JavaDoc condValue = productPromoCond.getString("condValue");
744         String JavaDoc otherValue = productPromoCond.getString("otherValue");
745         String JavaDoc inputParamEnumId = productPromoCond.getString("inputParamEnumId");
746         String JavaDoc operatorEnumId = productPromoCond.getString("operatorEnumId");
747
748         String JavaDoc partyId = cart.getPartyId();
749         GenericValue userLogin = cart.getUserLogin();
750         if (userLogin == null) {
751             userLogin = cart.getAutoUserLogin();
752         }
753
754         if (Debug.verboseOn()) Debug.logVerbose("Checking promotion condition: " + productPromoCond, module);
755         Integer JavaDoc compareBase = null;
756
757         if ("PPIP_PRODUCT_AMOUNT".equals(inputParamEnumId)) {
758             // for this type of promo force the operatorEnumId = PPC_EQ, effectively ignore that setting because the comparison is implied in the code
759
operatorEnumId = "PPC_EQ";
760             
761             // this type of condition requires items involved to not be involved in any other quantity consuming cond/action, and does not pro-rate the price, just uses the base price
762
double amountNeeded = 0.0;
763             if (UtilValidate.isNotEmpty(condValue)) {
764                 amountNeeded = Double.parseDouble(condValue);
765             }
766
767             // Debug.logInfo("Doing Amount Cond with Value: " + amountNeeded, module);
768

769             Set JavaDoc productIds = ProductPromoWorker.getPromoRuleCondProductIds(productPromoCond, delegator, nowTimestamp);
770
771             List JavaDoc lineOrderedByBasePriceList = cart.getLineListOrderedByBasePrice(false);
772             Iterator JavaDoc lineOrderedByBasePriceIter = lineOrderedByBasePriceList.iterator();
773             while (amountNeeded > 0 && lineOrderedByBasePriceIter.hasNext()) {
774                 ShoppingCartItem cartItem = (ShoppingCartItem) lineOrderedByBasePriceIter.next();
775                 // only include if it is in the productId Set for this check and if it is not a Promo (GWP) item
776
GenericValue product = cartItem.getProduct();
777                 String JavaDoc parentProductId = cartItem.getParentProductId();
778                 if (!cartItem.getIsPromo() &&
779                         (productIds.contains(cartItem.getProductId()) || (parentProductId != null && productIds.contains(parentProductId))) &&
780                         (product == null || !"N".equals(product.getString("includeInPromotions")))) {
781                     
782                     double basePrice = cartItem.getBasePrice();
783                     // get a rough price, round it up to an integer
784
double quantityNeeded = Math.ceil(amountNeeded / basePrice);
785                     
786                     // reduce amount still needed to qualify for promo (amountNeeded)
787
double quantity = cartItem.addPromoQuantityCandidateUse(quantityNeeded, productPromoCond, false);
788                     // get pro-rated amount based on discount
789
amountNeeded -= (quantity * basePrice);
790                 }
791             }
792
793             // Debug.logInfo("Doing Amount Cond with Value after finding applicable cart lines: " + amountNeeded, module);
794

795             // if amountNeeded > 0 then the promo condition failed, so remove candidate promo uses and increment the promoQuantityUsed to restore it
796
if (amountNeeded > 0) {
797                 // failed, reset the entire rule, ie including all other conditions that might have been done before
798
cart.resetPromoRuleUse(productPromoCond.getString("productPromoId"), productPromoCond.getString("productPromoRuleId"));
799                 compareBase = new Integer JavaDoc(-1);
800             } else {
801                 // we got it, the conditions are in place...
802
compareBase = new Integer JavaDoc(0);
803                 // NOTE: don't confirm promo rule use here, wait until actions are complete for the rule to do that
804
}
805         } else if ("PPIP_PRODUCT_TOTAL".equals(inputParamEnumId)) {
806             // this type of condition allows items involved to be involved in other quantity consuming cond/action, and does pro-rate the price
807
Double JavaDoc amountNeeded = Double.valueOf(condValue);
808             double amountAvailable = 0;
809
810             // Debug.logInfo("Doing Amount Not Counted Cond with Value: " + amountNeeded, module);
811

812             Set JavaDoc productIds = ProductPromoWorker.getPromoRuleCondProductIds(productPromoCond, delegator, nowTimestamp);
813
814             List JavaDoc lineOrderedByBasePriceList = cart.getLineListOrderedByBasePrice(false);
815             Iterator JavaDoc lineOrderedByBasePriceIter = lineOrderedByBasePriceList.iterator();
816             while (lineOrderedByBasePriceIter.hasNext()) {
817                 ShoppingCartItem cartItem = (ShoppingCartItem) lineOrderedByBasePriceIter.next();
818                 // only include if it is in the productId Set for this check and if it is not a Promo (GWP) item
819
GenericValue product = cartItem.getProduct();
820                 String JavaDoc parentProductId = cartItem.getParentProductId();
821                 if (!cartItem.getIsPromo() &&
822                         (productIds.contains(cartItem.getProductId()) || (parentProductId != null && productIds.contains(parentProductId))) &&
823                         (product == null || !"N".equals(product.getString("includeInPromotions")))) {
824                     
825                     // just count the entire sub-total of the item
826
amountAvailable += cartItem.getItemSubTotal();
827                 }
828             }
829
830             // Debug.logInfo("Doing Amount Not Counted Cond with Value after finding applicable cart lines: " + amountNeeded, module);
831

832             compareBase = new Integer JavaDoc(new Double JavaDoc(amountAvailable).compareTo(amountNeeded));
833         } else if ("PPIP_PRODUCT_QUANT".equals(inputParamEnumId)) {
834             // for this type of promo force the operatorEnumId = PPC_EQ, effectively ignore that setting because the comparison is implied in the code
835
operatorEnumId = "PPC_EQ";
836             
837             double quantityNeeded = 1.0;
838             if (UtilValidate.isNotEmpty(condValue)) {
839                 quantityNeeded = Double.parseDouble(condValue);
840             }
841
842             Set JavaDoc productIds = ProductPromoWorker.getPromoRuleCondProductIds(productPromoCond, delegator, nowTimestamp);
843
844             List JavaDoc lineOrderedByBasePriceList = cart.getLineListOrderedByBasePrice(false);
845             Iterator JavaDoc lineOrderedByBasePriceIter = lineOrderedByBasePriceList.iterator();
846             while (quantityNeeded > 0 && lineOrderedByBasePriceIter.hasNext()) {
847                 ShoppingCartItem cartItem = (ShoppingCartItem) lineOrderedByBasePriceIter.next();
848                 // only include if it is in the productId Set for this check and if it is not a Promo (GWP) item
849
GenericValue product = cartItem.getProduct();
850                 String JavaDoc parentProductId = cartItem.getParentProductId();
851                 if (!cartItem.getIsPromo() &&
852                         (productIds.contains(cartItem.getProductId()) || (parentProductId != null && productIds.contains(parentProductId))) &&
853                         (product == null || !"N".equals(product.getString("includeInPromotions")))) {
854                     // reduce quantity still needed to qualify for promo (quantityNeeded)
855
quantityNeeded -= cartItem.addPromoQuantityCandidateUse(quantityNeeded, productPromoCond, false);
856                 }
857             }
858
859             // if quantityNeeded > 0 then the promo condition failed, so remove candidate promo uses and increment the promoQuantityUsed to restore it
860
if (quantityNeeded > 0) {
861                 // failed, reset the entire rule, ie including all other conditions that might have been done before
862
cart.resetPromoRuleUse(productPromoCond.getString("productPromoId"), productPromoCond.getString("productPromoRuleId"));
863                 compareBase = new Integer JavaDoc(-1);
864             } else {
865                 // we got it, the conditions are in place...
866
compareBase = new Integer JavaDoc(0);
867                 // NOTE: don't confirm rpomo rule use here, wait until actions are complete for the rule to do that
868
}
869
870         /* replaced by PPIP_PRODUCT_QUANT
871         } else if ("PPIP_PRODUCT_ID_IC".equals(inputParamEnumId)) {
872             String candidateProductId = condValue;
873
874             if (candidateProductId == null) {
875                 // if null, then it's not in the cart
876                 compareBase = new Integer(1);
877             } else {
878                 // Debug.logInfo("Testing to see if productId \"" + candidateProductId + "\" is in the cart", module);
879                 List productCartItems = cart.findAllCartItems(candidateProductId);
880
881                 // don't count promotion items in this count...
882                 Iterator pciIter = productCartItems.iterator();
883                 while (pciIter.hasNext()) {
884                     ShoppingCartItem productCartItem = (ShoppingCartItem) pciIter.next();
885                     if (productCartItem.getIsPromo()) pciIter.remove();
886                 }
887
888                 if (productCartItems.size() > 0) {
889                     //Debug.logError("Item with productId \"" + candidateProductId + "\" IS in the cart", module);
890                     compareBase = new Integer(0);
891                 } else {
892                     //Debug.logError("Item with productId \"" + candidateProductId + "\" IS NOT in the cart", module);
893                     compareBase = new Integer(1);
894                 }
895             }
896         } else if ("PPIP_CATEGORY_ID_IC".equals(inputParamEnumId)) {
897             String productCategoryId = condValue;
898             Set productIds = new HashSet();
899
900             Iterator cartItemIter = cart.iterator();
901             while (cartItemIter.hasNext()) {
902                 ShoppingCartItem cartItem = (ShoppingCartItem) cartItemIter.next();
903                 if (!cartItem.getIsPromo()) {
904                     productIds.add(cartItem.getProductId());
905                 }
906             }
907
908             compareBase = new Integer(1);
909             // NOTE: this technique is efficient for a smaller number of items in the cart, if there are a lot of lines
910             //in the cart then a non-cached query with a set of productIds using the IN operator would be better
911             Iterator productIdIter = productIds.iterator();
912             while (productIdIter.hasNext()) {
913                 String productId = (String) productIdIter.next();
914
915                 // if a ProductCategoryMember exists for this productId and the specified productCategoryId
916                 List productCategoryMembers = delegator.findByAndCache("ProductCategoryMember", UtilMisc.toMap("productId", productId, "productCategoryId", productCategoryId));
917                 // and from/thru date within range
918                 productCategoryMembers = EntityUtil.filterByDate(productCategoryMembers, nowTimestamp);
919                 if (productCategoryMembers != null && productCategoryMembers.size() > 0) {
920                     // if any product is in category, set true and break
921                     // then 0 (equals), otherwise 1 (not equals)
922                     compareBase = new Integer(0);
923                     break;
924                 }
925             }
926         */

927         } else if ("PPIP_NEW_ACCT".equals(inputParamEnumId)) {
928             Double JavaDoc acctDays = cart.getPartyDaysSinceCreated(nowTimestamp);
929             if (acctDays == null) {
930                 // condition always fails if we don't know how many days since account created
931
return false;
932             }
933             compareBase = new Integer JavaDoc(acctDays.compareTo(Double.valueOf(condValue)));
934         } else if ("PPIP_PARTY_ID".equals(inputParamEnumId)) {
935             if (partyId != null) {
936                 compareBase = new Integer JavaDoc(partyId.compareTo(condValue));
937             } else {
938                 compareBase = new Integer JavaDoc(1);
939             }
940         } else if ("PPIP_PARTY_GRP_MEM".equals(inputParamEnumId)) {
941             if (UtilValidate.isEmpty(partyId)) {
942                 compareBase = new Integer JavaDoc(1);
943             } else {
944                 String JavaDoc groupPartyId = condValue;
945                 if (partyId.equals(groupPartyId)) {
946                     compareBase = new Integer JavaDoc(0);
947                 } else {
948                     // look for PartyRelationship with partyRelationshipTypeId=GROUP_ROLLUP, the partyIdTo is the group member, so the partyIdFrom is the groupPartyId
949
List JavaDoc partyRelationshipList = delegator.findByAndCache("PartyRelationship", UtilMisc.toMap("partyIdFrom", groupPartyId, "partyIdTo", partyId, "partyRelationshipTypeId", "GROUP_ROLLUP"));
950                     // and from/thru date within range
951
partyRelationshipList = EntityUtil.filterByDate(partyRelationshipList, true);
952                     // then 0 (equals), otherwise 1 (not equals)
953
if (partyRelationshipList != null && partyRelationshipList.size() > 0) {
954                         compareBase = new Integer JavaDoc(0);
955                     } else {
956                         compareBase = new Integer JavaDoc(1);
957                     }
958                 }
959             }
960         } else if ("PPIP_PARTY_CLASS".equals(inputParamEnumId)) {
961             if (UtilValidate.isEmpty(partyId)) {
962                 compareBase = new Integer JavaDoc(1);
963             } else {
964                 String JavaDoc partyClassificationGroupId = condValue;
965                 // find any PartyClassification
966
List JavaDoc partyClassificationList = delegator.findByAndCache("PartyClassification", UtilMisc.toMap("partyId", partyId, "partyClassificationGroupId", partyClassificationGroupId));
967                 // and from/thru date within range
968
partyClassificationList = EntityUtil.filterByDate(partyClassificationList, true);
969                 // then 0 (equals), otherwise 1 (not equals)
970
if (partyClassificationList != null && partyClassificationList.size() > 0) {
971                     compareBase = new Integer JavaDoc(0);
972                 } else {
973                     compareBase = new Integer JavaDoc(1);
974                 }
975             }
976         } else if ("PPIP_ROLE_TYPE".equals(inputParamEnumId)) {
977             if (partyId != null) {
978                 // if a PartyRole exists for this partyId and the specified roleTypeId
979
GenericValue partyRole = delegator.findByPrimaryKeyCache("PartyRole",
980                         UtilMisc.toMap("partyId", partyId, "roleTypeId", condValue));
981
982                 // then 0 (equals), otherwise 1 (not equals)
983
if (partyRole != null) {
984                     compareBase = new Integer JavaDoc(0);
985                 } else {
986                     compareBase = new Integer JavaDoc(1);
987                 }
988             } else {
989                 compareBase = new Integer JavaDoc(1);
990             }
991         } else if ("PPIP_ORDER_TOTAL".equals(inputParamEnumId)) {
992             Double JavaDoc orderSubTotal = new Double JavaDoc(cart.getSubTotalForPromotions());
993             if (Debug.verboseOn()) Debug.logVerbose("Doing order total compare: orderSubTotal=" + orderSubTotal, module);
994             compareBase = new Integer JavaDoc(orderSubTotal.compareTo(Double.valueOf(condValue)));
995         } else if ("PPIP_ORST_HIST".equals(inputParamEnumId)) {
996             // description="Order sub-total X in last Y Months"
997
if (partyId != null && userLogin != null) {
998                 // call the getOrderedSummaryInformation service to get the sub-total
999
int monthsToInclude = 12;
1000                if (otherValue != null) {
1001                    monthsToInclude = Integer.parseInt(condValue);
1002                }
1003                Map JavaDoc serviceIn = UtilMisc.toMap("partyId", partyId, "roleTypeId", "PLACING_CUSTOMER", "orderTypeId", "SALES_ORDER", "statusId", "ORDER_COMPLETED", "monthsToInclude", new Integer JavaDoc(monthsToInclude), "userLogin", userLogin);
1004                try {
1005                    Map JavaDoc result = dispatcher.runSync("getOrderedSummaryInformation", serviceIn);
1006                    if (ServiceUtil.isError(result)) {
1007                        Debug.logError("Error calling getOrderedSummaryInformation service for the PPIP_ORST_HIST ProductPromo condition input value: " + ServiceUtil.getErrorMessage(result), module);
1008                        return false;
1009                    } else {
1010                        Double JavaDoc orderSubTotal = (Double JavaDoc) result.get("totalSubRemainingAmount");
1011                        if (Debug.verboseOn()) Debug.logVerbose("Doing order history sub-total compare: orderSubTotal=" + orderSubTotal + ", for the last " + monthsToInclude + " months.", module);
1012                        compareBase = new Integer JavaDoc(orderSubTotal.compareTo(Double.valueOf(condValue)));
1013                    }
1014                } catch (GenericServiceException e) {
1015                    Debug.logError(e, "Error getting order history sub-total in the getOrderedSummaryInformation service, evaluating condition to false.", module);
1016                    return false;
1017                }
1018            } else {
1019                return false;
1020            }
1021        } else {
1022            Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderAnUnSupportedProductPromoCondInputParameterLhs", UtilMisc.toMap("inputParamEnumId",productPromoCond.getString("inputParamEnumId")), cart.getLocale()), module);
1023            return false;
1024        }
1025
1026        if (Debug.verboseOn()) Debug.logVerbose("Condition compare done, compareBase=" + compareBase, module);
1027
1028        if (compareBase != null) {
1029            int compare = compareBase.intValue();
1030            if ("PPC_EQ".equals(operatorEnumId)) {
1031                if (compare == 0) return true;
1032            } else if ("PPC_NEQ".equals(operatorEnumId)) {
1033                if (compare != 0) return true;
1034            } else if ("PPC_LT".equals(operatorEnumId)) {
1035                if (compare < 0) return true;
1036            } else if ("PPC_LTE".equals(operatorEnumId)) {
1037                if (compare <= 0) return true;
1038            } else if ("PPC_GT".equals(operatorEnumId)) {
1039                if (compare > 0) return true;
1040            } else if ("PPC_GTE".equals(operatorEnumId)) {
1041                if (compare >= 0) return true;
1042            } else {
1043                Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderAnUnSupportedProductPromoCondCondition", UtilMisc.toMap("operatorEnumId",operatorEnumId) , cart.getLocale()), module);
1044                return false;
1045            }
1046        }
1047        // default to not meeting the condition
1048
return false;
1049    }
1050    
1051    public static class ActionResultInfo {
1052        public boolean ranAction = false;
1053        public double totalDiscountAmount = 0;
1054        public double quantityLeftInAction = 0;
1055    }
1056
1057    /** returns true if the cart was changed and rules need to be re-evaluted */
1058    protected static ActionResultInfo performAction(GenericValue productPromoAction, ShoppingCart cart, GenericDelegator delegator, LocalDispatcher dispatcher, Timestamp JavaDoc nowTimestamp) throws GenericEntityException, CartItemModifyException {
1059        ActionResultInfo actionResultInfo = new ActionResultInfo();
1060        
1061        String JavaDoc productPromoActionEnumId = productPromoAction.getString("productPromoActionEnumId");
1062
1063        if ("PROMO_GWP".equals(productPromoActionEnumId)) {
1064            String JavaDoc productStoreId = cart.getProductStoreId();
1065            
1066            // the code was in there for this, so even though I don't think we want to restrict this, just adding this flag to make it easy to change; could make option dynamic, but now implied by the use limit
1067
boolean allowMultipleGwp = true;
1068            
1069            Integer JavaDoc itemLoc = findPromoItem(productPromoAction, cart);
1070            if (!allowMultipleGwp && itemLoc != null) {
1071                if (Debug.verboseOn()) Debug.logVerbose("Not adding promo item, already there; action: " + productPromoAction, module);
1072                actionResultInfo.ranAction = false;
1073            } else {
1074                double quantity = productPromoAction.get("quantity") == null ? 0.0 : productPromoAction.getDouble("quantity").doubleValue();
1075                
1076                List JavaDoc optionProductIds = FastList.newInstance();
1077                String JavaDoc productId = productPromoAction.getString("productId");
1078                
1079                GenericValue product = null;
1080                if (UtilValidate.isNotEmpty(productId)) {
1081                    // Debug.logInfo("======== Got GWP productId [" + productId + "]", module);
1082
product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
1083                    if (product == null) {
1084                        String JavaDoc errMsg = "GWP Product not found with ID [" + productId + "] for ProductPromoAction [" + productPromoAction.get("productPromoId") + ":" + productPromoAction.get("productPromoRuleId") + ":" + productPromoAction.get("productPromoActionSeqId") + "]";
1085                        Debug.logError(errMsg, module);
1086                        throw new CartItemModifyException(errMsg);
1087                    }
1088                    if ("Y".equals(product.getString("isVirtual"))) {
1089                        List JavaDoc productAssocs = EntityUtil.filterByDate(product.getRelatedCache("MainProductAssoc",
1090                                UtilMisc.toMap("productAssocTypeId", "PRODUCT_VARIANT"), UtilMisc.toList("sequenceNum")), true);
1091                        Iterator JavaDoc productAssocIter = productAssocs.iterator();
1092                        while (productAssocIter.hasNext()) {
1093                            GenericValue productAssoc = (GenericValue) productAssocIter.next();
1094                            optionProductIds.add(productAssoc.get("productIdTo"));
1095                        }
1096                        productId = null;
1097                        product = null;
1098                        // Debug.logInfo("======== GWP productId [" + productId + "] is a virtual with " + productAssocs.size() + " variants", module);
1099
} else {
1100                        // check inventory on this product, make sure it is available before going on
1101
//NOTE: even though the store may not require inventory for purchase, we will always require inventory for gifts
1102
try {
1103                            Map JavaDoc invReqResult = dispatcher.runSync("isStoreInventoryAvailable", UtilMisc.toMap("productStoreId", productStoreId, "productId", productId, "product", product, "quantity", new Double JavaDoc(quantity)));
1104                            if (ServiceUtil.isError(invReqResult)) {
1105                                Debug.logError("Error calling isStoreInventoryAvailable service, result is: " + invReqResult, module);
1106                                throw new CartItemModifyException((String JavaDoc) invReqResult.get(ModelService.ERROR_MESSAGE));
1107                            } else if (!"Y".equals((String JavaDoc) invReqResult.get("available"))) {
1108                                productId = null;
1109                                product = null;
1110                                Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderNotApplyingGwpBecauseProductIdIsOutOfStockForProductPromoAction", cart.getLocale()), module);
1111                            }
1112                        } catch (GenericServiceException e) {
1113                            String JavaDoc errMsg = "Fatal error calling inventory checking services: " + e.toString();
1114                            Debug.logError(e, errMsg, module);
1115                            throw new CartItemModifyException(errMsg);
1116                        }
1117                    }
1118                }
1119                
1120                // support multiple gift options if products are attached to the action, or if the productId on the action is a virtual product
1121
Set JavaDoc productIds = ProductPromoWorker.getPromoRuleActionProductIds(productPromoAction, delegator, nowTimestamp);
1122                if (productIds != null) {
1123                    optionProductIds.addAll(productIds);
1124                }
1125                
1126                // make sure these optionProducts have inventory...
1127
Iterator JavaDoc optionProductIdIter = optionProductIds.iterator();
1128                while (optionProductIdIter.hasNext()) {
1129                    String JavaDoc optionProductId = (String JavaDoc) optionProductIdIter.next();
1130
1131                    try {
1132                        Map JavaDoc invReqResult = dispatcher.runSync("isStoreInventoryAvailable", UtilMisc.toMap("productStoreId", productStoreId, "productId", optionProductId, "product", product, "quantity", new Double JavaDoc(quantity)));
1133                        if (ServiceUtil.isError(invReqResult)) {
1134                            Debug.logError("Error calling isStoreInventoryAvailable service, result is: " + invReqResult, module);
1135                            throw new CartItemModifyException((String JavaDoc) invReqResult.get(ModelService.ERROR_MESSAGE));
1136                        } else if (!"Y".equals((String JavaDoc) invReqResult.get("available"))) {
1137                            optionProductIdIter.remove();
1138                        }
1139                    } catch (GenericServiceException e) {
1140                        String JavaDoc errMsg = "Fatal error calling inventory checking services: " + e.toString();
1141                        Debug.logError(e, errMsg, module);
1142                        throw new CartItemModifyException(errMsg);
1143                    }
1144                }
1145                
1146                // check to see if any desired productIds have been selected for this promo action
1147
String JavaDoc alternateGwpProductId = cart.getDesiredAlternateGiftByAction(productPromoAction.getPrimaryKey());
1148                if (UtilValidate.isNotEmpty(alternateGwpProductId)) {
1149                    // also check to make sure this isn't a spoofed ID somehow, check to see if it is in the Set
1150
if (optionProductIds.contains(alternateGwpProductId)) {
1151                        if (UtilValidate.isNotEmpty(productId)) {
1152                            optionProductIds.add(productId);
1153                        }
1154                        optionProductIds.remove(alternateGwpProductId);
1155                        productId = alternateGwpProductId;
1156                        product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
1157                    } else {
1158                        Debug.logWarning(UtilProperties.getMessage(resource_error,"OrderAnAlternateGwpProductIdWasInPlaceButWasEitherNotValidOrIsNoLongerInStockForId", UtilMisc.toMap("alternateGwpProductId",alternateGwpProductId), cart.getLocale()), module);
1159                    }
1160                }
1161                
1162                // if product is null, get one from the productIds set
1163
if (product == null && optionProductIds.size() > 0) {
1164                    // get the first from an iterator and remove it since it will be the current one
1165
Iterator JavaDoc optionProductIdTempIter = optionProductIds.iterator();
1166                    productId = (String JavaDoc) optionProductIdTempIter.next();
1167                    optionProductIdTempIter.remove();
1168                    product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
1169                }
1170                    
1171                if (product == null) {
1172                    // no product found to add as GWP, just return
1173
return actionResultInfo;
1174                }
1175
1176                // pass null for cartLocation to add to end of cart, pass false for doPromotions to avoid infinite recursion
1177
ShoppingCartItem gwpItem = null;
1178                try {
1179                    // just leave the prodCatalogId null, this line won't be associated with a catalog
1180
String JavaDoc prodCatalogId = null;
1181                    gwpItem = ShoppingCartItem.makeItem(null, product, quantity, null, null, prodCatalogId, dispatcher, cart, false);
1182                    if (optionProductIds.size() > 0) {
1183                        gwpItem.setAlternativeOptionProductIds(optionProductIds);
1184                    } else {
1185                        gwpItem.setAlternativeOptionProductIds(null);
1186                    }
1187                } catch (CartItemModifyException e) {
1188                    int gwpItemIndex = cart.getItemIndex(gwpItem);
1189                    cart.removeCartItem(gwpItemIndex, dispatcher);
1190                    throw e;
1191                }
1192
1193                double discountAmount = -(quantity * gwpItem.getBasePrice());
1194
1195                doOrderItemPromoAction(productPromoAction, gwpItem, discountAmount, "amount", delegator);
1196                
1197                // set promo after create; note that to setQuantity we must clear this flag, setQuantity, then re-set the flag
1198
gwpItem.setIsPromo(true);
1199                if (Debug.verboseOn()) Debug.logVerbose("gwpItem adjustments: " + gwpItem.getAdjustments(), module);
1200
1201                actionResultInfo.ranAction = true;
1202                actionResultInfo.totalDiscountAmount = discountAmount;
1203            }
1204        } else if ("PROMO_FREE_SHIPPING".equals(productPromoActionEnumId)) {
1205            // this may look a bit funny: on each pass all rules that do free shipping will set their own rule id for it,
1206
// and on unapply if the promo and rule ids are the same then it will clear it; essentially on any pass
1207
// through the promos and rules if any free shipping should be there, it will be there
1208
cart.addFreeShippingProductPromoAction(productPromoAction);
1209            // don't consider this as a cart change?
1210
actionResultInfo.ranAction = true;
1211            // should probably set the totalDiscountAmount to something, but we have no idea what it will be, so leave at 0, will still get run
1212
} else if ("PROMO_PROD_DISC".equals(productPromoActionEnumId)) {
1213            double quantityDesired = productPromoAction.get("quantity") == null ? 1.0 : productPromoAction.getDouble("quantity").doubleValue();
1214            double startingQuantity = quantityDesired;
1215            double discountAmountTotal = 0;
1216
1217            Set JavaDoc productIds = ProductPromoWorker.getPromoRuleActionProductIds(productPromoAction, delegator, nowTimestamp);
1218
1219            List JavaDoc lineOrderedByBasePriceList = cart.getLineListOrderedByBasePrice(false);
1220            Iterator JavaDoc lineOrderedByBasePriceIter = lineOrderedByBasePriceList.iterator();
1221            while (quantityDesired > 0 && lineOrderedByBasePriceIter.hasNext()) {
1222                ShoppingCartItem cartItem = (ShoppingCartItem) lineOrderedByBasePriceIter.next();
1223                // only include if it is in the productId Set for this check and if it is not a Promo (GWP) item
1224
GenericValue product = cartItem.getProduct();
1225                String JavaDoc parentProductId = cartItem.getParentProductId();
1226                if (!cartItem.getIsPromo() &&
1227                        (productIds.contains(cartItem.getProductId()) || (parentProductId != null && productIds.contains(parentProductId))) &&
1228                        (product == null || !"N".equals(product.getString("includeInPromotions")))) {
1229                    // reduce quantity still needed to qualify for promo (quantityNeeded)
1230
double quantityUsed = cartItem.addPromoQuantityCandidateUse(quantityDesired, productPromoAction, false);
1231                    if (quantityUsed > 0) {
1232                        quantityDesired -= quantityUsed;
1233
1234                        // create an adjustment and add it to the cartItem that implements the promotion action
1235
double percentModifier = productPromoAction.get("amount") == null ? 0.0 : (productPromoAction.getDouble("amount").doubleValue()/100.0);
1236                        double lineAmount = quantityUsed * cartItem.getBasePrice();
1237                        double discountAmount = -(lineAmount * percentModifier);
1238                        discountAmountTotal += discountAmount;
1239                        // not doing this any more, now distributing among conditions and actions (see call below): doOrderItemPromoAction(productPromoAction, cartItem, discountAmount, "amount", delegator);
1240
}
1241                }
1242            }
1243
1244            if (quantityDesired == startingQuantity) {
1245                // couldn't find any cart items to give a discount to, don't consider action run
1246
actionResultInfo.ranAction = false;
1247            } else {
1248                double totalAmount = getCartItemsUsedTotalAmount(cart, productPromoAction);
1249                if (Debug.verboseOn()) Debug.logVerbose("Applying promo [" + productPromoAction.getPrimaryKey() + "]\n totalAmount=" + totalAmount + ", discountAmountTotal=" + discountAmountTotal, module);
1250                distributeDiscountAmount(discountAmountTotal, totalAmount, getCartItemsUsed(cart, productPromoAction), productPromoAction, delegator);
1251                actionResultInfo.ranAction = true;
1252                actionResultInfo.totalDiscountAmount = discountAmountTotal;
1253                actionResultInfo.quantityLeftInAction = quantityDesired;
1254            }
1255        } else if ("PROMO_PROD_AMDISC".equals(productPromoActionEnumId)) {
1256            double quantityDesired = productPromoAction.get("quantity") == null ? 1.0 : productPromoAction.getDouble("quantity").doubleValue();
1257            double startingQuantity = quantityDesired;
1258            double discountAmountTotal = 0;
1259            
1260            Set JavaDoc productIds = ProductPromoWorker.getPromoRuleActionProductIds(productPromoAction, delegator, nowTimestamp);
1261
1262            List JavaDoc lineOrderedByBasePriceList = cart.getLineListOrderedByBasePrice(false);
1263            Iterator JavaDoc lineOrderedByBasePriceIter = lineOrderedByBasePriceList.iterator();
1264            while (quantityDesired > 0 && lineOrderedByBasePriceIter.hasNext()) {
1265                ShoppingCartItem cartItem = (ShoppingCartItem) lineOrderedByBasePriceIter.next();
1266                // only include if it is in the productId Set for this check and if it is not a Promo (GWP) item
1267
String JavaDoc parentProductId = cartItem.getParentProductId();
1268                GenericValue product = cartItem.getProduct();
1269                if (!cartItem.getIsPromo() &&
1270                        (productIds.contains(cartItem.getProductId()) || (parentProductId != null && productIds.contains(parentProductId))) &&
1271                        (product == null || !"N".equals(product.getString("includeInPromotions")))) {
1272                    // reduce quantity still needed to qualify for promo (quantityNeeded)
1273
double quantityUsed = cartItem.addPromoQuantityCandidateUse(quantityDesired, productPromoAction, false);
1274                    quantityDesired -= quantityUsed;
1275
1276                    // create an adjustment and add it to the cartItem that implements the promotion action
1277
double discount = productPromoAction.get("amount") == null ? 0.0 : productPromoAction.getDouble("amount").doubleValue();
1278                    // don't allow the discount to be greater than the price
1279
if (discount > cartItem.getBasePrice()) {
1280                        discount = cartItem.getBasePrice();
1281                    }
1282                    double discountAmount = -(quantityUsed * discount);
1283                    discountAmountTotal += discountAmount;
1284                    // not doing this any more, now distributing among conditions and actions (see call below): doOrderItemPromoAction(productPromoAction, cartItem, discountAmount, "amount", delegator);
1285
}
1286            }
1287
1288            if (quantityDesired == startingQuantity) {
1289                // couldn't find any cart items to give a discount to, don't consider action run
1290
actionResultInfo.ranAction = false;
1291            } else {
1292                double totalAmount = getCartItemsUsedTotalAmount(cart, productPromoAction);
1293                if (Debug.verboseOn()) Debug.logVerbose("Applying promo [" + productPromoAction.getPrimaryKey() + "]\n totalAmount=" + totalAmount + ", discountAmountTotal=" + discountAmountTotal, module);
1294                distributeDiscountAmount(discountAmountTotal, totalAmount, getCartItemsUsed(cart, productPromoAction), productPromoAction, delegator);
1295                actionResultInfo.ranAction = true;
1296                actionResultInfo.totalDiscountAmount = discountAmountTotal;
1297                actionResultInfo.quantityLeftInAction = quantityDesired;
1298            }
1299        } else if ("PROMO_PROD_PRICE".equals(productPromoActionEnumId)) {
1300            // with this we want the set of used items to be one price, so total the price for all used items, subtract the amount we want them to cost, and create an adjustment for what is left
1301
double quantityDesired = productPromoAction.get("quantity") == null ? 1.0 : productPromoAction.getDouble("quantity").doubleValue();
1302            double desiredAmount = productPromoAction.get("amount") == null ? 0.0 : productPromoAction.getDouble("amount").doubleValue();
1303            double totalAmount = 0;
1304
1305            Set JavaDoc productIds = ProductPromoWorker.getPromoRuleActionProductIds(productPromoAction, delegator, nowTimestamp);
1306
1307            List JavaDoc cartItemsUsed = FastList.newInstance();
1308            List JavaDoc lineOrderedByBasePriceList = cart.getLineListOrderedByBasePrice(false);
1309            Iterator JavaDoc lineOrderedByBasePriceIter = lineOrderedByBasePriceList.iterator();
1310            while (quantityDesired > 0 && lineOrderedByBasePriceIter.hasNext()) {
1311                ShoppingCartItem cartItem = (ShoppingCartItem) lineOrderedByBasePriceIter.next();
1312                // only include if it is in the productId Set for this check and if it is not a Promo (GWP) item
1313
String JavaDoc parentProductId = cartItem.getParentProductId();
1314                GenericValue product = cartItem.getProduct();
1315                if (!cartItem.getIsPromo() && (productIds.contains(cartItem.getProductId()) || (parentProductId != null && productIds.contains(parentProductId))) &&
1316                        (product == null || !"N".equals(product.getString("includeInPromotions")))) {
1317                    // reduce quantity still needed to qualify for promo (quantityNeeded)
1318
double quantityUsed = cartItem.addPromoQuantityCandidateUse(quantityDesired, productPromoAction, false);
1319                    if (quantityUsed > 0) {
1320                        quantityDesired -= quantityUsed;
1321                        totalAmount += quantityUsed * cartItem.getBasePrice();
1322                        cartItemsUsed.add(cartItem);
1323                    }
1324                }
1325            }
1326
1327            if (totalAmount > desiredAmount && quantityDesired == 0) {
1328                double discountAmountTotal = -(totalAmount - desiredAmount);
1329                distributeDiscountAmount(discountAmountTotal, totalAmount, cartItemsUsed, productPromoAction, delegator);
1330                actionResultInfo.ranAction = true;
1331                actionResultInfo.totalDiscountAmount = discountAmountTotal;
1332                // no use setting the quantityLeftInAction because that does not apply for buy X for $Y type promotions, it is all or nothing
1333
} else {
1334                actionResultInfo.ranAction = false;
1335                // clear out any action uses for this so they don't become part of anything else
1336
cart.resetPromoRuleUse(productPromoAction.getString("productPromoId"), productPromoAction.getString("productPromoRuleId"));
1337            }
1338        } else if ("PROMO_ORDER_PERCENT".equals(productPromoActionEnumId)) {
1339            double percentage = -(productPromoAction.get("amount") == null ? 0.0 : (productPromoAction.getDouble("amount").doubleValue() / 100.0));
1340            double amount = cart.getSubTotalForPromotions() * percentage;
1341            if (amount != 0) {
1342                doOrderPromoAction(productPromoAction, cart, amount, "amount", delegator);
1343                actionResultInfo.ranAction = true;
1344                actionResultInfo.totalDiscountAmount = amount;
1345            }
1346        } else if ("PROMO_ORDER_AMOUNT".equals(productPromoActionEnumId)) {
1347            double amount = -(productPromoAction.get("amount") == null ? 0.0 : productPromoAction.getDouble("amount").doubleValue());
1348            // if amount is greater than the order sub total, set equal to order sub total, this normally wouldn't happen because there should be a condition that the order total be above a certain amount, but just in case...
1349
double subTotal = cart.getSubTotalForPromotions();
1350            if (-amount > subTotal) {
1351                amount = -subTotal;
1352            }
1353            if (amount != 0) {
1354                doOrderPromoAction(productPromoAction, cart, amount, "amount", delegator);
1355                actionResultInfo.ranAction = true;
1356                actionResultInfo.totalDiscountAmount = amount;
1357            }
1358        } else if ("PROMO_PROD_SPPRC".equals(productPromoActionEnumId)) {
1359            // if there are productIds associated with the action then restrict to those productIds, otherwise apply for all products
1360
Set JavaDoc productIds = ProductPromoWorker.getPromoRuleActionProductIds(productPromoAction, delegator, nowTimestamp);
1361
1362            // go through the cart items and for each product that has a specialPromoPrice use that price
1363
Iterator JavaDoc cartItemIter = cart.items().iterator();
1364            while (cartItemIter.hasNext()) {
1365                ShoppingCartItem cartItem = (ShoppingCartItem) cartItemIter.next();
1366                String JavaDoc itemProductId = cartItem.getProductId();
1367                if (UtilValidate.isEmpty(itemProductId)) {
1368                    continue;
1369                }
1370                
1371                if (productIds.size() > 0 && !productIds.contains(itemProductId)) {
1372                    continue;
1373                }
1374                
1375                if (cartItem.getSpecialPromoPrice() == null) {
1376                    continue;
1377                }
1378                
1379                // get difference between basePrice and specialPromoPrice and adjust for that
1380
double difference = -(cartItem.getBasePrice() - cartItem.getSpecialPromoPrice().doubleValue());
1381
1382                if (difference != 0.0) {
1383                    double quantityUsed = cartItem.addPromoQuantityCandidateUse(cartItem.getQuantity(), productPromoAction, false);
1384                    if (quantityUsed > 0) {
1385                        double amount = difference * quantityUsed;
1386                        doOrderItemPromoAction(productPromoAction, cartItem, amount, "amount", delegator);
1387                        actionResultInfo.ranAction = true;
1388                        actionResultInfo.totalDiscountAmount = amount;
1389                    }
1390                }
1391            }
1392        } else {
1393            Debug.logError("An un-supported productPromoActionType was used: " + productPromoActionEnumId + ", not performing any action", module);
1394            actionResultInfo.ranAction = false;
1395        }
1396
1397        if (actionResultInfo.ranAction) {
1398            // 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
1399
cart.confirmPromoRuleUse(productPromoAction.getString("productPromoId"), productPromoAction.getString("productPromoRuleId"));
1400        } else {
1401            cart.resetPromoRuleUse(productPromoAction.getString("productPromoId"), productPromoAction.getString("productPromoRuleId"));
1402        }
1403
1404        return actionResultInfo;
1405    }
1406    
1407    protected static List JavaDoc getCartItemsUsed(ShoppingCart cart, GenericValue productPromoAction) {
1408        List JavaDoc cartItemsUsed = FastList.newInstance();
1409        Iterator JavaDoc cartItemsIter = cart.iterator();
1410        while (cartItemsIter.hasNext()) {
1411            ShoppingCartItem cartItem = (ShoppingCartItem) cartItemsIter.next();
1412            double quantityUsed = cartItem.getPromoQuantityCandidateUseActionAndAllConds(productPromoAction);
1413            if (quantityUsed > 0) {
1414                cartItemsUsed.add(cartItem);
1415            }
1416        }
1417        return cartItemsUsed;
1418    }
1419    
1420    protected static double getCartItemsUsedTotalAmount(ShoppingCart cart, GenericValue productPromoAction) {
1421        double totalAmount = 0;
1422        Iterator JavaDoc cartItemsIter = cart.iterator();
1423        while (cartItemsIter.hasNext()) {
1424            ShoppingCartItem cartItem = (ShoppingCartItem) cartItemsIter.next();
1425            double quantityUsed = cartItem.getPromoQuantityCandidateUseActionAndAllConds(productPromoAction);
1426            if (quantityUsed > 0) {
1427                totalAmount += quantityUsed * cartItem.getBasePrice();
1428            }
1429        }
1430        return totalAmount;
1431    }
1432    
1433    protected static void distributeDiscountAmount(double discountAmountTotal, double totalAmount, List JavaDoc cartItemsUsed, GenericValue productPromoAction, GenericDelegator delegator) {
1434        double discountAmount = discountAmountTotal;
1435        // distribute the discount evenly weighted according to price over the order items that the individual quantities came from; avoids a number of issues with tax/shipping calc, inclusion in the sub-total for other promotions, etc
1436
Iterator JavaDoc cartItemsUsedIter = cartItemsUsed.iterator();
1437        while (cartItemsUsedIter.hasNext()) {
1438            ShoppingCartItem cartItem = (ShoppingCartItem) cartItemsUsedIter.next();
1439            // to minimize rounding issues use the remaining total for the last one, otherwise use a calculated value
1440
if (cartItemsUsedIter.hasNext()) {
1441                double quantityUsed = cartItem.getPromoQuantityCandidateUseActionAndAllConds(productPromoAction);
1442                double ratioOfTotal = (quantityUsed * cartItem.getBasePrice()) / totalAmount;
1443                double weightedAmount = ratioOfTotal * discountAmountTotal;
1444                // round the weightedAmount to 2 decimal places, ie a whole number of cents or 2 decimal place monetary units
1445
weightedAmount = weightedAmount * 100.0;
1446                long roundedAmount = Math.round(weightedAmount);
1447                weightedAmount = ((double) roundedAmount) / 100.0;
1448                discountAmount -= weightedAmount;
1449                doOrderItemPromoAction(productPromoAction, cartItem, weightedAmount, "amount", delegator);
1450            } else {
1451                // last one, just use discountAmount
1452
doOrderItemPromoAction(productPromoAction, cartItem, discountAmount, "amount", delegator);
1453            }
1454        }
1455        // this is the old way that causes problems: doOrderPromoAction(productPromoAction, cart, discountAmount, "amount", delegator);
1456
}
1457
1458    protected static Integer JavaDoc findPromoItem(GenericValue productPromoAction, ShoppingCart cart) {
1459        List JavaDoc cartItems = cart.items();
1460
1461        for (int i = 0; i < cartItems.size(); i++) {
1462            ShoppingCartItem checkItem = (ShoppingCartItem) cartItems.get(i);
1463
1464            if (checkItem.getIsPromo()) {
1465                // found a promo item, see if it has a matching adjustment on it
1466
Iterator JavaDoc checkOrderAdjustments = UtilMisc.toIterator(checkItem.getAdjustments());
1467                while (checkOrderAdjustments != null && checkOrderAdjustments.hasNext()) {
1468                    GenericValue checkOrderAdjustment = (GenericValue) checkOrderAdjustments.next();
1469                    if (productPromoAction.getString("productPromoId").equals(checkOrderAdjustment.get("productPromoId")) &&
1470                        productPromoAction.getString("productPromoRuleId").equals(checkOrderAdjustment.get("productPromoRuleId")) &&
1471                        productPromoAction.getString("productPromoActionSeqId").equals(checkOrderAdjustment.get("productPromoActionSeqId"))) {
1472                        return new Integer JavaDoc(i);
1473                    }
1474                }
1475            }
1476        }
1477        return null;
1478    }
1479
1480    public static void doOrderItemPromoAction(GenericValue productPromoAction, ShoppingCartItem cartItem, double amount, String JavaDoc amountField, GenericDelegator delegator) {
1481        GenericValue orderAdjustment = delegator.makeValue("OrderAdjustment",
1482                UtilMisc.toMap("orderAdjustmentTypeId", "PROMOTION_ADJUSTMENT", amountField, new Double JavaDoc(amount),
1483                    "productPromoId", productPromoAction.get("productPromoId"),
1484                    "productPromoRuleId", productPromoAction.get("productPromoRuleId"),
1485                    "productPromoActionSeqId", productPromoAction.get("productPromoActionSeqId")));
1486
1487        // if an orderAdjustmentTypeId was included, override the default
1488
if (UtilValidate.isNotEmpty(productPromoAction.getString("orderAdjustmentTypeId"))) {
1489            orderAdjustment.set("orderAdjustmentTypeId", productPromoAction.get("orderAdjustmentTypeId"));
1490        }
1491
1492        cartItem.addAdjustment(orderAdjustment);
1493    }
1494
1495    public static void doOrderPromoAction(GenericValue productPromoAction, ShoppingCart cart, double amount, String JavaDoc amountField, GenericDelegator delegator) {
1496        GenericValue orderAdjustment = delegator.makeValue("OrderAdjustment",
1497                UtilMisc.toMap("orderAdjustmentTypeId", "PROMOTION_ADJUSTMENT", amountField, new Double JavaDoc(amount),
1498                    "productPromoId", productPromoAction.get("productPromoId"),
1499                    "productPromoRuleId", productPromoAction.get("productPromoRuleId"),
1500                    "productPromoActionSeqId", productPromoAction.get("productPromoActionSeqId")));
1501
1502        // if an orderAdjustmentTypeId was included, override the default
1503
if (UtilValidate.isNotEmpty(productPromoAction.getString("orderAdjustmentTypeId"))) {
1504            orderAdjustment.set("orderAdjustmentTypeId", productPromoAction.get("orderAdjustmentTypeId"));
1505        }
1506
1507        cart.addAdjustment(orderAdjustment);
1508    }
1509
1510    protected static Integer JavaDoc findAdjustment(GenericValue productPromoAction, List JavaDoc adjustments) {
1511        for (int i = 0; i < adjustments.size(); i++) {
1512            GenericValue checkOrderAdjustment = (GenericValue) adjustments.get(i);
1513
1514            if (productPromoAction.getString("productPromoId").equals(checkOrderAdjustment.get("productPromoId")) &&
1515                productPromoAction.getString("productPromoRuleId").equals(checkOrderAdjustment.get("productPromoRuleId")) &&
1516                productPromoAction.getString("productPromoActionSeqId").equals(checkOrderAdjustment.get("productPromoActionSeqId"))) {
1517                return new Integer JavaDoc(i);
1518            }
1519        }
1520        return null;
1521    }
1522
1523    public static Set JavaDoc getPromoRuleCondProductIds(GenericValue productPromoCond, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp) throws GenericEntityException {
1524        // get a cached list for the whole promo and filter it as needed, this for better efficiency in caching
1525
List JavaDoc productPromoCategoriesAll = delegator.findByAndCache("ProductPromoCategory", UtilMisc.toMap("productPromoId", productPromoCond.get("productPromoId")));
1526        List JavaDoc productPromoCategories = EntityUtil.filterByAnd(productPromoCategoriesAll, UtilMisc.toMap("productPromoRuleId", "_NA_", "productPromoCondSeqId", "_NA_"));
1527        productPromoCategories.addAll(EntityUtil.filterByAnd(productPromoCategoriesAll, UtilMisc.toMap("productPromoRuleId", productPromoCond.get("productPromoRuleId"), "productPromoCondSeqId", productPromoCond.get("productPromoCondSeqId"))));
1528
1529        List JavaDoc productPromoProductsAll = delegator.findByAndCache("ProductPromoProduct", UtilMisc.toMap("productPromoId", productPromoCond.get("productPromoId")));
1530        List JavaDoc productPromoProducts = EntityUtil.filterByAnd(productPromoProductsAll, UtilMisc.toMap("productPromoRuleId", "_NA_", "productPromoCondSeqId", "_NA_"));
1531        productPromoProducts.addAll(EntityUtil.filterByAnd(productPromoProductsAll, UtilMisc.toMap("productPromoRuleId", productPromoCond.get("productPromoRuleId"), "productPromoCondSeqId", productPromoCond.get("productPromoCondSeqId"))));
1532
1533        Set JavaDoc productIds = new HashSet JavaDoc();
1534        makeProductPromoIdSet(productIds, productPromoCategories, productPromoProducts, delegator, nowTimestamp, false);
1535        return productIds;
1536    }
1537
1538    public static Set JavaDoc getPromoRuleActionProductIds(GenericValue productPromoAction, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp) throws GenericEntityException {
1539        // get a cached list for the whole promo and filter it as needed, this for better efficiency in caching
1540
List JavaDoc productPromoCategoriesAll = delegator.findByAndCache("ProductPromoCategory", UtilMisc.toMap("productPromoId", productPromoAction.get("productPromoId")));
1541        List JavaDoc productPromoCategories = EntityUtil.filterByAnd(productPromoCategoriesAll, UtilMisc.toMap("productPromoRuleId", "_NA_", "productPromoActionSeqId", "_NA_"));
1542        productPromoCategories.addAll(EntityUtil.filterByAnd(productPromoCategoriesAll, UtilMisc.toMap("productPromoRuleId", productPromoAction.get("productPromoRuleId"), "productPromoActionSeqId", productPromoAction.get("productPromoActionSeqId"))));
1543
1544        List JavaDoc productPromoProductsAll = delegator.findByAndCache("ProductPromoProduct", UtilMisc.toMap("productPromoId", productPromoAction.get("productPromoId")));
1545        List JavaDoc productPromoProducts = EntityUtil.filterByAnd(productPromoProductsAll, UtilMisc.toMap("productPromoRuleId", "_NA_", "productPromoActionSeqId", "_NA_"));
1546        productPromoProducts.addAll(EntityUtil.filterByAnd(productPromoProductsAll, UtilMisc.toMap("productPromoRuleId", productPromoAction.get("productPromoRuleId"), "productPromoActionSeqId", productPromoAction.get("productPromoActionSeqId"))));
1547
1548        Set JavaDoc productIds = new HashSet JavaDoc();
1549        makeProductPromoIdSet(productIds, productPromoCategories, productPromoProducts, delegator, nowTimestamp, false);
1550        return productIds;
1551    }
1552
1553    public static void makeProductPromoIdSet(Set JavaDoc productIds, List JavaDoc productPromoCategories, List JavaDoc productPromoProducts, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp, boolean filterOldProducts) throws GenericEntityException {
1554        // do the includes
1555
handleProductPromoCategories(productIds, productPromoCategories, "PPPA_INCLUDE", delegator, nowTimestamp);
1556        handleProductPromoProducts(productIds, productPromoProducts, "PPPA_INCLUDE");
1557
1558        // do the excludes
1559
handleProductPromoCategories(productIds, productPromoCategories, "PPPA_EXCLUDE", delegator, nowTimestamp);
1560        handleProductPromoProducts(productIds, productPromoProducts, "PPPA_EXCLUDE");
1561
1562        // do the always includes
1563
handleProductPromoCategories(productIds, productPromoCategories, "PPPA_ALWAYS", delegator, nowTimestamp);
1564        handleProductPromoProducts(productIds, productPromoProducts, "PPPA_ALWAYS");
1565    }
1566
1567    public static void makeProductPromoCondActionIdSets(String JavaDoc productPromoId, Set JavaDoc productIdsCond, Set JavaDoc productIdsAction, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp) throws GenericEntityException {
1568        makeProductPromoCondActionIdSets(productPromoId, productIdsCond, productIdsAction, delegator, nowTimestamp, false);
1569    }
1570    
1571    public static void makeProductPromoCondActionIdSets(String JavaDoc productPromoId, Set JavaDoc productIdsCond, Set JavaDoc productIdsAction, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp, boolean filterOldProducts) throws GenericEntityException {
1572        if (nowTimestamp == null) {
1573            nowTimestamp = UtilDateTime.nowTimestamp();
1574        }
1575
1576        List JavaDoc productPromoCategoriesAll = delegator.findByAndCache("ProductPromoCategory", UtilMisc.toMap("productPromoId", productPromoId));
1577        List JavaDoc productPromoProductsAll = delegator.findByAndCache("ProductPromoProduct", UtilMisc.toMap("productPromoId", productPromoId));
1578
1579        List JavaDoc productPromoProductsCond = FastList.newInstance();
1580        List JavaDoc productPromoCategoriesCond = FastList.newInstance();
1581        List JavaDoc productPromoProductsAction = FastList.newInstance();
1582        List JavaDoc productPromoCategoriesAction = FastList.newInstance();
1583
1584        Iterator JavaDoc productPromoProductsAllIter = productPromoProductsAll.iterator();
1585        while (productPromoProductsAllIter.hasNext()) {
1586            GenericValue productPromoProduct = (GenericValue) productPromoProductsAllIter.next();
1587            // if the rule id is null then this is a global promo one, so always include
1588
if (!"_NA_".equals(productPromoProduct.getString("productPromoCondSeqId")) || "_NA_".equals(productPromoProduct.getString("productPromoRuleId"))) {
1589                productPromoProductsCond.add(productPromoProduct);
1590            }
1591            if (!"_NA_".equals(productPromoProduct.getString("productPromoActionSeqId")) || "_NA_".equals(productPromoProduct.getString("productPromoRuleId"))) {
1592                productPromoProductsAction.add(productPromoProduct);
1593            }
1594        }
1595        Iterator JavaDoc productPromoCategoriesAllIter = productPromoCategoriesAll.iterator();
1596        while (productPromoCategoriesAllIter.hasNext()) {
1597            GenericValue productPromoCategory = (GenericValue) productPromoCategoriesAllIter.next();
1598            if (!"_NA_".equals(productPromoCategory.getString("productPromoCondSeqId")) || "_NA_".equals(productPromoCategory.getString("productPromoRuleId"))) {
1599                productPromoCategoriesCond.add(productPromoCategory);
1600            }
1601            if (!"_NA_".equals(productPromoCategory.getString("productPromoActionSeqId")) || "_NA_".equals(productPromoCategory.getString("productPromoRuleId"))) {
1602                productPromoCategoriesAction.add(productPromoCategory);
1603            }
1604        }
1605
1606        makeProductPromoIdSet(productIdsCond, productPromoCategoriesCond, productPromoProductsCond, delegator, nowTimestamp, filterOldProducts);
1607        makeProductPromoIdSet(productIdsAction, productPromoCategoriesAction, productPromoProductsAction, delegator, nowTimestamp, filterOldProducts);
1608        
1609        // last of all filterOldProducts, done here to make sure no product gets looked up twice
1610
if (filterOldProducts) {
1611            Iterator JavaDoc productIdsCondIter = productIdsCond.iterator();
1612            while (productIdsCondIter.hasNext()) {
1613                String JavaDoc productId = (String JavaDoc) productIdsCondIter.next();
1614                if (isProductOld(productId, delegator, nowTimestamp)) {
1615                    productIdsCondIter.remove();
1616                }
1617            }
1618            Iterator JavaDoc productIdsActionIter = productIdsAction.iterator();
1619            while (productIdsActionIter.hasNext()) {
1620                String JavaDoc productId = (String JavaDoc) productIdsActionIter.next();
1621                if (isProductOld(productId, delegator, nowTimestamp)) {
1622                    productIdsActionIter.remove();
1623                }
1624            }
1625        }
1626    }
1627    
1628    protected static boolean isProductOld(String JavaDoc productId, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp) throws GenericEntityException {
1629        GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
1630        if (product != null) {
1631            Timestamp JavaDoc salesDiscontinuationDate = product.getTimestamp("salesDiscontinuationDate");
1632            if (salesDiscontinuationDate != null && salesDiscontinuationDate.before(nowTimestamp)) {
1633                return true;
1634            }
1635        }
1636        return false;
1637    }
1638
1639    protected static void handleProductPromoCategories(Set JavaDoc productIds, List JavaDoc productPromoCategories, String JavaDoc productPromoApplEnumId, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp) throws GenericEntityException {
1640        boolean include = !"PPPA_EXCLUDE".equals(productPromoApplEnumId);
1641        Set JavaDoc productCategoryIds = new HashSet JavaDoc();
1642        Map JavaDoc productCategoryGroupSetListMap = new HashMap JavaDoc();
1643        
1644        Iterator JavaDoc productPromoCategoryIter = productPromoCategories.iterator();
1645        while (productPromoCategoryIter.hasNext()) {
1646            GenericValue productPromoCategory = (GenericValue) productPromoCategoryIter.next();
1647            if (productPromoApplEnumId.equals(productPromoCategory.getString("productPromoApplEnumId"))) {
1648                Set JavaDoc tempCatIdSet = new HashSet JavaDoc();
1649                if ("Y".equals(productPromoCategory.getString("includeSubCategories"))) {
1650                    ProductSearch.getAllSubCategoryIds(productPromoCategory.getString("productCategoryId"), tempCatIdSet, delegator, nowTimestamp);
1651                } else {
1652                    tempCatIdSet.add(productPromoCategory.getString("productCategoryId"));
1653                }
1654                
1655                String JavaDoc andGroupId = productPromoCategory.getString("andGroupId");
1656                if ("_NA_".equals(andGroupId)) {
1657                    productCategoryIds.addAll(tempCatIdSet);
1658                } else {
1659                    List JavaDoc catIdSetList = (List JavaDoc) productCategoryGroupSetListMap.get(andGroupId);
1660                    if (catIdSetList == null) {
1661                        catIdSetList = FastList.newInstance();
1662                    }
1663                    catIdSetList.add(tempCatIdSet);
1664                }
1665            }
1666        }
1667        
1668        // for the ones with andGroupIds, if there is only one category move it to the productCategoryIds Set
1669
// also remove all empty SetLists and Sets
1670
Iterator JavaDoc pcgslmeIter = productCategoryGroupSetListMap.entrySet().iterator();
1671        while (pcgslmeIter.hasNext()) {
1672            Map.Entry JavaDoc entry = (Map.Entry JavaDoc) pcgslmeIter.next();
1673            List JavaDoc catIdSetList = (List JavaDoc) entry.getValue();
1674            if (catIdSetList.size() == 0) {
1675                pcgslmeIter.remove();
1676            } else if (catIdSetList.size() == 1) {
1677                Set JavaDoc catIdSet = (Set JavaDoc) catIdSetList.iterator().next();
1678                if (catIdSet.size() == 0) {
1679                    pcgslmeIter.remove();
1680                } else {
1681                    // if there is only one set in the list since the set will be or'ed anyway, just add them all to the productCategoryIds Set
1682
productCategoryIds.addAll(catIdSet);
1683                    pcgslmeIter.remove();
1684                }
1685            }
1686        }
1687
1688        // now that the category Set and Map are setup, take care of the productCategoryIds Set first
1689
getAllProductIds(productCategoryIds, productIds, delegator, nowTimestamp, include);
1690        
1691        // now handle the productCategoryGroupSetListMap
1692
// if a set has more than one category (because of an include sub-cats) then do an or
1693
// all lists will have more than category because of the pre-pass that was done, so and them together
1694
Iterator JavaDoc pcgslmIter = productCategoryGroupSetListMap.entrySet().iterator();
1695        while (pcgslmIter.hasNext()) {
1696            Map.Entry JavaDoc entry = (Map.Entry JavaDoc) pcgslmIter.next();
1697            List JavaDoc catIdSetList = (List JavaDoc) entry.getValue();
1698            // get all productIds for this catIdSetList
1699
List JavaDoc productIdSetList = FastList.newInstance();
1700            
1701            Iterator JavaDoc cidslIter = catIdSetList.iterator();
1702            while (cidslIter.hasNext()) {
1703                // make a Set of productIds including all ids from all categories
1704
Set JavaDoc catIdSet = (Set JavaDoc) cidslIter.next();
1705                Set JavaDoc groupProductIdSet = new HashSet JavaDoc();
1706                getAllProductIds(catIdSet, groupProductIdSet, delegator, nowTimestamp, true);
1707                productIdSetList.add(groupProductIdSet);
1708            }
1709            
1710            // now go through all productId sets and only include IDs that are in all sets
1711
// by definition if each id must be in all categories, then it must be in the first, so go through the first and drop each one that is not in all others
1712
Set JavaDoc firstProductIdSet = (Set JavaDoc) productIdSetList.remove(0);
1713            Iterator JavaDoc productIdSetIter = productIdSetList.iterator();
1714            while (productIdSetIter.hasNext()) {
1715                Set JavaDoc productIdSet = (Set JavaDoc) productIdSetIter.next();
1716                firstProductIdSet.retainAll(productIdSet);
1717            }
1718            
1719            /* the old way of doing it, not as efficient, recoded above using the retainAll operation, pretty handy
1720            Iterator firstProductIdIter = firstProductIdSet.iterator();
1721            while (firstProductIdIter.hasNext()) {
1722                String curProductId = (String) firstProductIdIter.next();
1723                
1724                boolean allContainProductId = true;
1725                Iterator productIdSetIter = productIdSetList.iterator();
1726                while (productIdSetIter.hasNext()) {
1727                    Set productIdSet = (Set) productIdSetIter.next();
1728                    if (!productIdSet.contains(curProductId)) {
1729                        allContainProductId = false;
1730                        break;
1731                    }
1732                }
1733                
1734                if (!allContainProductId) {
1735                    firstProductIdIter.remove();
1736                }
1737            }
1738             */

1739            
1740            if (firstProductIdSet.size() >= 0) {
1741                if (include) {
1742                    productIds.addAll(firstProductIdSet);
1743                } else {
1744                    productIds.removeAll(firstProductIdSet);
1745                }
1746            }
1747        }
1748    }
1749    
1750    protected static void getAllProductIds(Set JavaDoc productCategoryIdSet, Set JavaDoc productIdSet, GenericDelegator delegator, Timestamp JavaDoc nowTimestamp, boolean include) throws GenericEntityException {
1751        Iterator JavaDoc productCategoryIdIter = productCategoryIdSet.iterator();
1752        while (productCategoryIdIter.hasNext()) {
1753            String JavaDoc productCategoryId = (String JavaDoc) productCategoryIdIter.next();
1754            // get all product category memebers, filter by date
1755
List JavaDoc productCategoryMembers = delegator.findByAndCache("ProductCategoryMember", UtilMisc.toMap("productCategoryId", productCategoryId));
1756            productCategoryMembers = EntityUtil.filterByDate(productCategoryMembers, nowTimestamp);
1757            Iterator JavaDoc productCategoryMemberIter = productCategoryMembers.iterator();
1758            while (productCategoryMemberIter.hasNext()) {
1759                GenericValue productCategoryMember = (GenericValue) productCategoryMemberIter.next();
1760                String JavaDoc productId = productCategoryMember.getString("productId");
1761                if (include) {
1762                    productIdSet.add(productId);
1763                } else {
1764                    productIdSet.remove(productId);
1765                }
1766            }
1767        }
1768    }
1769
1770    protected static void handleProductPromoProducts(Set JavaDoc productIds, List JavaDoc productPromoProducts, String JavaDoc productPromoApplEnumId) throws GenericEntityException {
1771        boolean include = !"PPPA_EXCLUDE".equals(productPromoApplEnumId);
1772        Iterator JavaDoc productPromoProductIter = productPromoProducts.iterator();
1773        while (productPromoProductIter.hasNext()) {
1774            GenericValue productPromoProduct = (GenericValue) productPromoProductIter.next();
1775            if (productPromoApplEnumId.equals(productPromoProduct.getString("productPromoApplEnumId"))) {
1776                String JavaDoc productId = productPromoProduct.getString("productId");
1777                if (include) {
1778                    productIds.add(productId);
1779                } else {
1780                    productIds.remove(productId);
1781                }
1782            }
1783        }
1784    }
1785    
1786    protected static class UseLimitException extends Exception JavaDoc {
1787        public UseLimitException(String JavaDoc str) {
1788            super(str);
1789        }
1790    }
1791}
1792
Popular Tags