View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.businessobject;
17  
18  import java.sql.Date;
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.kuali.ole.coa.businessobject.Account;
28  import org.kuali.ole.coa.businessobject.ObjectCode;
29  import org.kuali.ole.sys.context.SpringContext;
30  import org.kuali.ole.sys.document.AccountingDocument;
31  import org.kuali.ole.sys.document.service.AccountPresenceService;
32  import org.kuali.rice.core.api.datetime.DateTimeService;
33  import org.kuali.rice.kew.api.exception.WorkflowException;
34  import org.kuali.rice.krad.document.Document;
35  import org.kuali.rice.krad.service.DocumentService;
36  import org.kuali.rice.krad.util.ObjectUtils;
37  
38  /**
39   * This class helps implement AccountingLine overrides. It is not persisted itself, but it simplifies working with the persisted
40   * codes. Instances break the code into components. Static methods help with the AccountingLine.
41   */
42  public class AccountingLineOverride {
43      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountingLineOverride.class);
44  
45      /**
46       * These codes are the way the override is persisted in the AccountingLine.
47       */
48      public static final class CODE { // todo: use JDK 1.5 enum
49          public static final String NONE = "NONE";
50          public static final String EXPIRED_ACCOUNT = "EXPIRED_ACCOUNT";
51          public static final String NON_BUDGETED_OBJECT = "NON_BUDGETED_OBJECT";
52          public static final String TRANSACTION_EXCEEDS_REMAINING_BUDGET = "TRANSACTION_EXCEEDS_REMAINING_BUDGET";
53          public static final String EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT = "EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT";
54          public static final String NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET";
55          public static final String EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET";
56          public static final String EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET = "EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET";
57          public static final String NON_FRINGE_ACCOUNT_USED = "NON_FRINGE_ACCOUNT_USED";
58          public static final String EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED = "EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED";
59      }
60  
61      /**
62       * These are the somewhat independent components of an override.
63       */
64      public static final class COMPONENT { // todo: use JDK 1.5 enum
65          public static final Integer EXPIRED_ACCOUNT = new Integer(1);
66          public static final Integer NON_BUDGETED_OBJECT = new Integer(2);
67          public static final Integer TRANSACTION_EXCEEDS_REMAINING_BUDGET = new Integer(3);
68          public static final Integer NON_FRINGE_ACCOUNT_USED = new Integer(8);
69      }
70  
71      /**
72       * The names of the AccountingLine properties that the processForOutput() and determineNeededOverrides() methods use. Callers of
73       * those methods may need to refresh these fields from OJB.
74       */
75      public static final List<String> REFRESH_FIELDS = Collections.unmodifiableList(Arrays.asList(new String[] { "account", "objectCode" }));
76  
77      /**
78       * This holds an instance of every valid override, mapped by code.
79       */
80      private static final Map<String, AccountingLineOverride> codeToOverrideMap = new HashMap<String, AccountingLineOverride>();
81  
82      /**
83       * This holds an instance of every valid override, mapped by components.
84       */
85      private static final Map componentsToOverrideMap = new HashMap();
86  
87      static {
88          // populate the code map
89          new AccountingLineOverride(CODE.NONE, new Integer[] {});
90          new AccountingLineOverride(CODE.EXPIRED_ACCOUNT,
91          // todo: use JDK 1.5 ... args
92              new Integer[] { COMPONENT.EXPIRED_ACCOUNT });
93          new AccountingLineOverride(CODE.NON_BUDGETED_OBJECT, new Integer[] { COMPONENT.NON_BUDGETED_OBJECT });
94          new AccountingLineOverride(CODE.TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
95          new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.NON_BUDGETED_OBJECT });
96          new AccountingLineOverride(CODE.NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.NON_BUDGETED_OBJECT, COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
97          new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
98          new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_NON_BUDGETED_OBJECT_AND_TRANSACTION_EXCEEDS_REMAINING_BUDGET, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.NON_BUDGETED_OBJECT, COMPONENT.TRANSACTION_EXCEEDS_REMAINING_BUDGET });
99          new AccountingLineOverride(CODE.NON_FRINGE_ACCOUNT_USED, new Integer[] { COMPONENT.NON_FRINGE_ACCOUNT_USED });
100         new AccountingLineOverride(CODE.EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED, new Integer[] { COMPONENT.EXPIRED_ACCOUNT, COMPONENT.NON_FRINGE_ACCOUNT_USED });
101     }
102 
103     private final String code;
104     private final Set components;
105 
106     /**
107      * This private constructor is for the static initializer.
108      * 
109      * @param myCode
110      * @param myComponents
111      */
112     private AccountingLineOverride(String myCode, Integer[] myComponents) {
113         code = myCode;
114         components = componentsAsSet(myComponents);
115         codeToOverrideMap.put(code, this);
116         componentsToOverrideMap.put(components, this);
117     }
118 
119     /**
120      * Checks whether this override contains the given component.
121      * 
122      * @param component
123      * @return whether this override contains the given component.
124      */
125     public boolean hasComponent(Integer component) {
126         return components.contains(component);
127     }
128 
129     /**
130      * Gets the code of this override.
131      * 
132      * @return the code of this override.
133      */
134     public String getCode() {
135         return code;
136     }
137 
138     /**
139      * Gets the components of this override.
140      * 
141      * @return the components of this override.
142      */
143     private Set getComponents() {
144         return components;
145     }
146 
147     /**
148      * @see java.lang.Object#toString()
149      */
150     @Override
151     public String toString() {
152         return "AccountingLineOverride (code " + code + ", components " + components + ")";
153     }
154 
155     /**
156      * Returns the AccountingLineOverride that has the components of this AccountingLineOverride minus any components not in the
157      * given mask. This is like <code>&amp;</code>(a bit-wise and), if the components were bits.
158      * 
159      * @param mask
160      * @return the AccountingLineOverride that has the components of this AccountingLineOverride minus any components not in the
161      *         given mask.
162      * @throws IllegalArgumentException if there is no such valid combination of components
163      */
164     public AccountingLineOverride mask(AccountingLineOverride mask) {
165         Set key = maskComponents(mask);
166         if (!isValidComponentSet(key)) {
167             throw new IllegalArgumentException("invalid component set " + key);
168         }
169         return valueOf(key);
170     }
171 
172     /**
173      * Returns the Set of components that this override and the given override have in common.
174      * 
175      * @param mask
176      * @return the Set of components that this override and the given override have in common.
177      */
178     private Set maskComponents(AccountingLineOverride mask) {
179         Set retval = new HashSet(components);
180         retval.retainAll(mask.getComponents());
181         return retval;
182     }
183 
184     /**
185      * Returns whether this override, when masked by the given override, is valid. Some combinations of components have no override
186      * code defined.
187      * 
188      * @param mask
189      * @return whether this override, when masked by the given override, is valid.
190      */
191     public boolean isValidMask(AccountingLineOverride mask) {
192         return isValidComponentSet(maskComponents(mask));
193     }
194 
195     /**
196      * Returns whether the given String is a valid override code.
197      * 
198      * @param code
199      * @return whether the given String is a valid override code.
200      */
201     public static boolean isValidCode(String code) {
202         return codeToOverrideMap.containsKey(code);
203     }
204 
205     /**
206      * Returns whether the given Integers are a valid set of components. Some combinations of components are invalid and have no
207      * code defined.
208      * 
209      * @param components
210      * @return whether the given Integers are a valid set of components.
211      */
212     public static boolean isValidComponentSet(Integer[] components) {
213         return isValidComponentSet(componentsAsSet(components));
214     }
215 
216     private static boolean isValidComponentSet(Set components) { // todo: JDK 1.5 generic Set
217         return componentsToOverrideMap.containsKey(components);
218     }
219 
220     /**
221      * Factory method from code.
222      * 
223      * @param code the override code
224      * @return the AccountingLineOverride instance corresponding to the given code.
225      * @throws IllegalArgumentException if the given code is not valid
226      */
227     public static AccountingLineOverride valueOf(String code) {
228         if (!isValidCode(code)) {
229             throw new IllegalArgumentException("invalid code " + code);
230         }
231         return (AccountingLineOverride) codeToOverrideMap.get(code); // todo: JDK 1.5 generic Map instead of cast
232     }
233 
234     /**
235      * Factory method from components.
236      * 
237      * @param components the override components, treated as a set
238      * @return the AccountingLineOverride instance corresponding to the given component set.
239      * @throws IllegalArgumentException if the given set of components is not valid
240      */
241     public static AccountingLineOverride valueOf(Integer[] components) {
242         Set key = componentsAsSet(components);
243         if (!isValidComponentSet(key)) {
244             throw new IllegalArgumentException("invalid component set " + key);
245         }
246         return valueOf(key);
247     }
248 
249     public static AccountingLineOverride valueOf(Set components) {
250         return (AccountingLineOverride) componentsToOverrideMap.get(components); // todo: JDK 1.5 generic Map instead of cast
251     }
252 
253     private static Set componentsAsSet(Integer[] components) {
254         return Collections.unmodifiableSet(new HashSet(Arrays.asList(components)));
255     }
256 
257     /**
258      * On the given AccountingLine, converts override input checkboxes from a Struts Form into a persistable override code.
259      * 
260      * @param line
261      */
262     public static void populateFromInput(AccountingLine line) {
263         // todo: this logic won't work if a single account checkbox might also stands for NON_FRINGE_ACCOUNT_USED; needs thought
264 
265         Set overrideInputComponents = new HashSet();
266         if (line.getAccountExpiredOverride()) {
267             overrideInputComponents.add(COMPONENT.EXPIRED_ACCOUNT);
268         }
269         if (line.isObjectBudgetOverride()) {
270             overrideInputComponents.add(COMPONENT.NON_BUDGETED_OBJECT);
271         }
272         if (!isValidComponentSet(overrideInputComponents)) {
273             // todo: error for invalid override checkbox combinations, for which there is no override code
274         }
275         line.setOverrideCode(valueOf(overrideInputComponents).getCode());
276     }
277 
278     /**
279      * Prepares the given AccountingLine in a Struts Action for display by a JSP. This means converting the override code to
280      * checkboxes for display and input, as well as analysing the accounting line and determining which override checkboxes are
281      * needed.
282      * 
283      * @param line
284      */
285     public static void processForOutput(AccountingDocument document ,AccountingLine line) {
286         AccountingLineOverride fromCurrentCode = valueOf(line.getOverrideCode());
287         AccountingLineOverride needed = determineNeededOverrides(document,line);
288         line.setAccountExpiredOverride(fromCurrentCode.hasComponent(COMPONENT.EXPIRED_ACCOUNT));
289         line.setAccountExpiredOverrideNeeded(needed.hasComponent(COMPONENT.EXPIRED_ACCOUNT));
290         line.setObjectBudgetOverride(fromCurrentCode.hasComponent(COMPONENT.NON_BUDGETED_OBJECT));
291         line.setObjectBudgetOverrideNeeded(needed.hasComponent(COMPONENT.NON_BUDGETED_OBJECT));
292     }
293 
294     /**
295      * Determines what overrides the given line needs.
296      * 
297      * @param line
298      * @return what overrides the given line needs.
299      */
300     public static AccountingLineOverride determineNeededOverrides(AccountingDocument document ,AccountingLine line) {
301         boolean isDocumentFinalOrProcessed = false;
302        if(ObjectUtils.isNotNull(document)) {
303            AccountingDocument accountingDocument = (AccountingDocument) document;
304            isDocumentFinalOrProcessed = accountingDocument.isDocumentFinalOrProcessed();
305        }
306 
307         Set neededOverrideComponents = new HashSet();
308         if (needsExpiredAccountOverride(line, isDocumentFinalOrProcessed)) {
309             neededOverrideComponents.add(COMPONENT.EXPIRED_ACCOUNT);
310         }
311         if (needsObjectBudgetOverride(line.getAccount(), line.getObjectCode())) {
312             neededOverrideComponents.add(COMPONENT.NON_BUDGETED_OBJECT);
313         }
314 
315         if (!isValidComponentSet(neededOverrideComponents)) {
316             // todo: error for invalid override checkbox combinations, for which there is no override code
317         }
318         return valueOf(neededOverrideComponents);
319     }
320 
321     /**
322      * Returns whether the given account needs an expired account override.
323      * 
324      * @param account
325      * @return whether the given account needs an expired account override.
326      */
327     public static boolean needsExpiredAccountOverride(Account account) {
328         return !ObjectUtils.isNull(account) && account.isActive() && account.isExpired();
329     }
330 
331     /**
332      * Returns whether the given account needs an expired account override.
333      * 
334      * @param account
335      * @return whether the given account needs an expired account override.
336      */
337     public static boolean needsExpiredAccountOverride(AccountingLine line, boolean isDocumentFinalOrProcessed ) {
338         if(isDocumentFinalOrProcessed){
339             if(CODE.EXPIRED_ACCOUNT.equals(line.getOverrideCode())) {
340                 return true;
341             }
342             else {
343                 return false;
344             }
345         }
346         else {
347             return  !ObjectUtils.isNull(line.getAccount()) && line.getAccount().isActive() && line.getAccount().isExpired();
348         }
349     }
350 
351     /**
352      * Returns whether the given account needs an expired account override.
353      *
354      * @param account
355      * @return whether the given account needs an expired account override.
356      */
357     public static boolean needsNonFringAccountOverride(Account account) {
358         return !ObjectUtils.isNull(account) && account.isActive() && !account.isAccountsFringesBnftIndicator();
359     }
360 
361     /**
362      * Returns whether the given object code needs an object budget override
363      * 
364      * @param account
365      * @return whether the given object code needs an object budget override
366      */
367     public static boolean needsObjectBudgetOverride(Account account, ObjectCode objectCode) {
368         return !ObjectUtils.isNull(account) && !ObjectUtils.isNull(objectCode) && account.isActive() && !SpringContext.getBean(AccountPresenceService.class).isObjectCodeBudgetedForAccountPresence(account, objectCode);
369     }
370 
371     public static Document getDocument(AccountingLine line) {
372         Document document = null;
373         try {
374             document = SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(line.getDocumentNumber());
375 
376        }catch(WorkflowException exception) {
377            LOG.error("Unable to locate document for documentId :: " + line.getDocumentNumber() );
378        }
379 
380        return document;
381     }
382 }