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.module.cg.document;
17  
18  import static org.kuali.ole.sys.OLEPropertyConstants.AWARD_ACCOUNTS;
19  import static org.kuali.ole.sys.OLEPropertyConstants.AWARD_PROJECT_DIRECTORS;
20  import static org.kuali.ole.sys.OLEPropertyConstants.AWARD_SUBCONTRACTORS;
21  import static org.kuali.ole.sys.OLEPropertyConstants.DOCUMENT;
22  import static org.kuali.ole.sys.OLEPropertyConstants.NEW_MAINTAINABLE_OBJECT;
23  
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.kuali.ole.module.cg.businessobject.Award;
32  import org.kuali.ole.module.cg.businessobject.AwardAccount;
33  import org.kuali.ole.module.cg.businessobject.AwardOrganization;
34  import org.kuali.ole.module.cg.businessobject.AwardProjectDirector;
35  import org.kuali.ole.module.cg.businessobject.AwardSubcontractor;
36  import org.kuali.ole.module.cg.businessobject.CGProjectDirector;
37  import org.kuali.ole.module.cg.businessobject.Proposal;
38  import org.kuali.ole.module.cg.document.validation.impl.AwardRuleUtil;
39  import org.kuali.ole.sys.OLEConstants;
40  import org.kuali.ole.sys.OLEKeyConstants;
41  import org.kuali.ole.sys.OLEPropertyConstants;
42  import org.kuali.ole.sys.context.SpringContext;
43  import org.kuali.ole.sys.document.FinancialSystemMaintainable;
44  import org.kuali.rice.kew.api.WorkflowDocument;
45  import org.kuali.rice.kim.api.identity.Person;
46  import org.kuali.rice.kim.api.identity.PersonService;
47  import org.kuali.rice.kns.document.MaintenanceDocument;
48  import org.kuali.rice.krad.bo.DocumentHeader;
49  import org.kuali.rice.krad.bo.PersistableBusinessObject;
50  import org.kuali.rice.krad.maintenance.MaintenanceLock;
51  import org.kuali.rice.krad.service.BusinessObjectService;
52  import org.kuali.rice.krad.util.GlobalVariables;
53  import org.kuali.rice.krad.util.ObjectUtils;
54  
55  /**
56   * Methods for the Award maintenance document UI.
57   */
58  public class AwardMaintainableImpl extends FinancialSystemMaintainable {
59      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AwardMaintainableImpl.class);
60      /**
61       * Constructs an AwardMaintainableImpl.
62       */
63      public AwardMaintainableImpl() {
64          super();
65      }
66  
67      /**
68       * Constructs a AwardMaintainableImpl.
69       * 
70       * @param award
71       */
72      public AwardMaintainableImpl(Award award) {
73          super(award);
74          this.setBoClass(award.getClass());
75      }
76  
77      /**
78       * This method is called for refreshing the Agency before display to show the full name in case the agency number was changed by
79       * hand before any submit that causes a redisplay.
80       */
81      @Override
82      public void processAfterRetrieve() {
83          refreshAward(false);
84          super.processAfterRetrieve();
85      }
86  
87      /**
88       * This method is called for refreshing the Agency before a save to display the full name in case the agency number was changed
89       * by hand just before the save.
90       */
91      @Override
92      public void prepareForSave() {
93          refreshAward(false);
94          List<AwardProjectDirector> directors = getAward().getAwardProjectDirectors();
95          if (directors.size() == 1) {
96              directors.get(0).setAwardPrimaryProjectDirectorIndicator(true);
97          }
98          List<AwardOrganization> organizations = getAward().getAwardOrganizations();
99          if (organizations.size() == 1) {
100             organizations.get(0).setAwardPrimaryOrganizationIndicator(true);
101         }
102         // need to populate the synthetic keys for these records
103         // since we can not depend on the keys which exist, we need to determine all those which could match
104         // so we can avoid them
105         List<AwardSubcontractor> awardSubcontractors = getAward().getAwardSubcontractors();
106         if (awardSubcontractors != null && !awardSubcontractors.isEmpty()) {
107             // convert the list into a map of lists containing the used award subcontractor number/amendment number
108             Map<String,List<AwardSubcontractor>> subcontractorAwardMap = new HashMap<String, List<AwardSubcontractor>>();
109             List<AwardSubcontractor> newSubcontractorRecords = new ArrayList<AwardSubcontractor>();
110             for (AwardSubcontractor awardSubcontractor : awardSubcontractors) {
111                 if ( !StringUtils.isBlank(awardSubcontractor.getAwardSubcontractorNumber()) ) {
112                     // already has key - add to map
113                     if ( !subcontractorAwardMap.containsKey(awardSubcontractor.getSubcontractorNumber()) ) {
114                         subcontractorAwardMap.put(awardSubcontractor.getSubcontractorNumber(), new ArrayList<AwardSubcontractor>() );
115                     }
116                     subcontractorAwardMap.get(awardSubcontractor.getSubcontractorNumber()).add(awardSubcontractor);
117                 } else {
118                     // new record, add to new map
119                     newSubcontractorRecords.add(awardSubcontractor);
120                 }
121             }
122             
123             // now, loop over the new records
124             for (AwardSubcontractor awardSubcontractor : newSubcontractorRecords) {
125                 String awardSubcontractorNumber = "1";
126                 String awardSubcontractorAmendmentNumber = "1";
127                 // get the other ones for the same subcontractor
128                 List<AwardSubcontractor> oldSubcontractors = subcontractorAwardMap.get(awardSubcontractor.getSubcontractorNumber());
129                 if ( oldSubcontractors != null ) {
130                     // we have a hit - find the first non-used number                    
131                     // build an array from the unsorted list
132                     boolean[][] nums = new boolean[100][100];
133                     for ( AwardSubcontractor oldSub : oldSubcontractors ) {
134                         try {
135                             nums[Integer.valueOf( oldSub.getAwardSubcontractorNumber() )][Integer.valueOf( oldSub.getAwardSubcontractorAmendmentNumber() )] = true;
136                         } catch ( NumberFormatException ex ) {
137                             // do nothing
138                             LOG.warn( "Unexpected non-integer award subcontractor / amendment number: " + oldSub.getAwardSubcontractorNumber() + " / " + oldSub.getAwardSubcontractorAmendmentNumber() );
139                         }
140                     }
141                     // iterate over the array to get the first empty value
142                     // loop over the awardSubcontractorNumbers first
143                     boolean foundNumbers = false;
144                     for ( int i = 1; i <= 99; i++ ) {
145                         for ( int j = 1; j <= 99; j++ ) {
146                             if ( !nums[j][i] ) { 
147                                 // save the values 
148                                 awardSubcontractorNumber = Integer.toString(j); 
149                                 awardSubcontractorAmendmentNumber = Integer.toString(i); 
150                                 // mark the cell as used before the next pass 
151                                 nums[j][i] = true; 
152                                 // just a flag to allow us to break out of both loops 
153                                 foundNumbers = true; 
154                                 break; 
155                             }
156                         }
157                         if ( foundNumbers ) {
158                             break;
159                         }
160                         // JHK - yes, this will break down if there are more than 9801 subcontracts
161                         // however, the UI will probably break down far before then...
162                     }
163                 }
164                 awardSubcontractor.setAwardSubcontractorNumber(awardSubcontractorNumber);
165                 awardSubcontractor.setAwardSubcontractorAmendmentNumber(awardSubcontractorAmendmentNumber);
166             }
167             
168         }
169         
170         
171 // The implementation below is **** - allows for easy key collisions         
172 //        List<AwardSubcontractor> awardSubcontractors = getAward().getAwardSubcontractors();
173 //        int i = 0;
174 //        if (awardSubcontractors != null && !awardSubcontractors.isEmpty()) {
175 //            for (AwardSubcontractor awardSubcontractor : awardSubcontractors) {
176 //                i++;
177 //                if (StringUtils.isBlank(awardSubcontractor.getAwardSubcontractorAmendmentNumber())) {
178 //                    awardSubcontractor.setAwardSubcontractorAmendmentNumber("" + i);
179 //                }
180 //                if (StringUtils.isBlank(awardSubcontractor.getAwardSubcontractorNumber())) {
181 //                    awardSubcontractor.setAwardSubcontractorNumber("" + i);
182 //                }
183 //            }
184 //        }
185 
186         super.prepareForSave();
187     }
188 
189     /**
190      * This method is called for refreshing the Agency after a lookup to display its full name without AJAX.
191      * 
192      * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map,
193      *      org.kuali.rice.kns.document.MaintenanceDocument)
194      */
195     @SuppressWarnings("unchecked")
196     @Override
197     public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
198         if (StringUtils.equals("proposalLookupable", (String) fieldValues.get(OLEConstants.REFRESH_CALLER))) {
199 
200             boolean awarded = AwardRuleUtil.isProposalAwarded(getAward());
201             if (awarded) {
202                 String pathToMaintainable = DOCUMENT + "." + NEW_MAINTAINABLE_OBJECT;
203                 GlobalVariables.getMessageMap().addToErrorPath(pathToMaintainable);
204                 GlobalVariables.getMessageMap().putError(OLEPropertyConstants.PROPOSAL_NUMBER, OLEKeyConstants.ERROR_AWARD_PROPOSAL_AWARDED, new String[] { getAward().getProposalNumber().toString() });
205                 GlobalVariables.getMessageMap().removeFromErrorPath(pathToMaintainable);
206             }
207             
208             // SEE KULCG-315 for details on why this code is commented out.
209             // if (AwardRuleUtil.isProposalInactive(getAward())) {
210             // GlobalVariables.getMessageMap().putError(OLEPropertyConstants.PROPOSAL_NUMBER,
211             // OLEKeyConstants.ERROR_AWARD_PROPOSAL_INACTIVE, new String[] { getAward().getProposalNumber().toString() });
212             // }
213 
214             // copy over proposal values after refresh
215             if (!awarded) {
216                 refreshAward(true);
217                 fieldValues.put(OLEConstants.REFERENCES_TO_REFRESH, "proposal");
218                 super.refresh(refreshCaller, fieldValues, document);
219                 getAward().populateFromProposal(getAward().getProposal());
220                 refreshAward(true);
221             }
222         } else {
223             refreshAward(OLEConstants.KUALI_LOOKUPABLE_IMPL.equals(fieldValues.get(OLEConstants.REFRESH_CALLER)));
224             super.refresh(refreshCaller, fieldValues, document);
225         }
226 
227     }
228 
229     /**
230      * Load related objects from the database as needed.
231      * 
232      * @param refreshFromLookup
233      */
234     private void refreshAward(boolean refreshFromLookup) {
235         Award award = getAward();
236         award.refreshNonUpdateableReferences();
237 
238         getNewCollectionLine(AWARD_SUBCONTRACTORS).refreshNonUpdateableReferences();
239         getNewCollectionLine(AWARD_PROJECT_DIRECTORS).refreshNonUpdateableReferences();
240         getNewCollectionLine(AWARD_ACCOUNTS).refreshNonUpdateableReferences();
241 
242         // the org list doesn't need any refresh
243         refreshNonUpdateableReferences(award.getAwardOrganizations());
244         refreshNonUpdateableReferences(award.getAwardAccounts());
245         refreshNonUpdateableReferences(award.getAwardSubcontractors());
246         refreshAwardProjectDirectors(refreshFromLookup);
247     }
248 
249     /**
250      * Refresh the collection of associated AwardProjectDirectors.
251      * 
252      * @param refreshFromLookup a lookup returns only the primary key, so ignore the secondary key when true
253      */
254     private void refreshAwardProjectDirectors(boolean refreshFromLookup) {
255         if (refreshFromLookup) {
256             getNewCollectionLine(AWARD_PROJECT_DIRECTORS).refreshNonUpdateableReferences();
257             refreshNonUpdateableReferences(getAward().getAwardProjectDirectors());
258 
259             getNewCollectionLine(AWARD_ACCOUNTS).refreshNonUpdateableReferences();
260             refreshNonUpdateableReferences(getAward().getAwardAccounts());
261         }
262         else {
263             refreshWithSecondaryKey((AwardProjectDirector) getNewCollectionLine(AWARD_PROJECT_DIRECTORS));
264             for (AwardProjectDirector projectDirector : getAward().getAwardProjectDirectors()) {
265                 refreshWithSecondaryKey(projectDirector);
266             }
267 
268             refreshWithSecondaryKey((AwardAccount) getNewCollectionLine(AWARD_ACCOUNTS));
269             for (AwardAccount account : getAward().getAwardAccounts()) {
270                 refreshWithSecondaryKey(account);
271             }
272         }
273     }
274 
275     /**
276      * @param collection
277      */
278     private static void refreshNonUpdateableReferences(Collection<? extends PersistableBusinessObject> collection) {
279         for (PersistableBusinessObject item : collection) {
280             item.refreshNonUpdateableReferences();
281         }
282     }
283 
284     /**
285      * Refreshes the reference to ProjectDirector, giving priority to its secondary key. Any secondary key that it has may be user
286      * input, so that overrides the primary key, setting the primary key. If its primary key is blank or nonexistent, then leave the
287      * current reference as it is, because it may be a nonexistent instance which is holding the secondary key (the username, i.e.,
288      * principalName) so we can redisplay it to the user for correction. If it only has a primary key then use that, because it may
289      * be coming from the database, without any user input.
290      * 
291      * @param director the ProjectDirector to refresh
292      */
293     private static void refreshWithSecondaryKey(CGProjectDirector director) {
294         Person cgdir = director.getProjectDirector();
295         if (ObjectUtils.isNotNull(cgdir)) {
296             String secondaryKey = cgdir.getPrincipalName();
297             if (StringUtils.isNotBlank(secondaryKey)) {
298                 Person dir = SpringContext.getBean(PersonService.class).getPersonByPrincipalName(secondaryKey);
299                 director.setPrincipalId(dir == null ? null : dir.getPrincipalId());
300             }
301             if (StringUtils.isNotBlank(director.getPrincipalId())) {
302                 Person person = SpringContext.getBean(PersonService.class).getPerson(director.getPrincipalId());
303                 if (person != null) {
304                     ((PersistableBusinessObject) director).refreshNonUpdateableReferences();
305                 }
306             }
307         }
308     }
309 
310     /**
311      * Gets the underlying Award.
312      * 
313      * @return
314      */
315     public Award getAward() {
316         return (Award) getBusinessObject();
317     }
318 
319     /**
320      * Called for refreshing the {@link Subcontractor} on {@link ProposalSubcontractor} before adding to the proposalSubcontractors
321      * collection on the proposal. this is to ensure that the summary fields are show correctly. i.e. {@link Subcontractor} name
322      * 
323      * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#addNewLineToCollection(java.lang.String)
324      */
325     @Override
326     public void addNewLineToCollection(String collectionName) {
327         refreshAward(false);
328         super.addNewLineToCollection(collectionName);
329     }
330 
331     /**
332      * This method overrides the parent method to check the status of the award document and change the linked
333      * {@link ProposalStatus} to A (Approved) if the {@link Award} is now in approved status.
334      * 
335      * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.krad.bo.DocumentHeader)
336      */
337     @Override
338     public void doRouteStatusChange(DocumentHeader header) {
339         super.doRouteStatusChange(header);
340 
341         Award award = getAward();
342         WorkflowDocument workflowDoc = header.getWorkflowDocument();
343 
344         // Use the isProcessed() method so this code is only executed when the final approval occurs
345         if (workflowDoc.isProcessed()) {
346             Proposal proposal = award.getProposal();
347             proposal.setProposalStatusCode(Proposal.AWARD_CODE);
348             SpringContext.getBean(BusinessObjectService.class).save(proposal);
349         }
350 
351     }
352 
353     public List<MaintenanceLock> generateMaintenanceLocks() {
354         List<MaintenanceLock> locks = super.generateMaintenanceLocks();
355         return locks;
356     }
357 }