001/* 002 * Copyright 2007 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.ole.module.cg.document; 017 018import static org.kuali.ole.sys.OLEPropertyConstants.AWARD_ACCOUNTS; 019import static org.kuali.ole.sys.OLEPropertyConstants.AWARD_PROJECT_DIRECTORS; 020import static org.kuali.ole.sys.OLEPropertyConstants.AWARD_SUBCONTRACTORS; 021import static org.kuali.ole.sys.OLEPropertyConstants.DOCUMENT; 022import static org.kuali.ole.sys.OLEPropertyConstants.NEW_MAINTAINABLE_OBJECT; 023 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import org.apache.commons.lang.StringUtils; 031import org.kuali.ole.module.cg.businessobject.Award; 032import org.kuali.ole.module.cg.businessobject.AwardAccount; 033import org.kuali.ole.module.cg.businessobject.AwardOrganization; 034import org.kuali.ole.module.cg.businessobject.AwardProjectDirector; 035import org.kuali.ole.module.cg.businessobject.AwardSubcontractor; 036import org.kuali.ole.module.cg.businessobject.CGProjectDirector; 037import org.kuali.ole.module.cg.businessobject.Proposal; 038import org.kuali.ole.module.cg.document.validation.impl.AwardRuleUtil; 039import org.kuali.ole.sys.OLEConstants; 040import org.kuali.ole.sys.OLEKeyConstants; 041import org.kuali.ole.sys.OLEPropertyConstants; 042import org.kuali.ole.sys.context.SpringContext; 043import org.kuali.ole.sys.document.FinancialSystemMaintainable; 044import org.kuali.rice.kew.api.WorkflowDocument; 045import org.kuali.rice.kim.api.identity.Person; 046import org.kuali.rice.kim.api.identity.PersonService; 047import org.kuali.rice.kns.document.MaintenanceDocument; 048import org.kuali.rice.krad.bo.DocumentHeader; 049import org.kuali.rice.krad.bo.PersistableBusinessObject; 050import org.kuali.rice.krad.maintenance.MaintenanceLock; 051import org.kuali.rice.krad.service.BusinessObjectService; 052import org.kuali.rice.krad.util.GlobalVariables; 053import org.kuali.rice.krad.util.ObjectUtils; 054 055/** 056 * Methods for the Award maintenance document UI. 057 */ 058public class AwardMaintainableImpl extends FinancialSystemMaintainable { 059 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AwardMaintainableImpl.class); 060 /** 061 * Constructs an AwardMaintainableImpl. 062 */ 063 public AwardMaintainableImpl() { 064 super(); 065 } 066 067 /** 068 * Constructs a AwardMaintainableImpl. 069 * 070 * @param award 071 */ 072 public AwardMaintainableImpl(Award award) { 073 super(award); 074 this.setBoClass(award.getClass()); 075 } 076 077 /** 078 * This method is called for refreshing the Agency before display to show the full name in case the agency number was changed by 079 * hand before any submit that causes a redisplay. 080 */ 081 @Override 082 public void processAfterRetrieve() { 083 refreshAward(false); 084 super.processAfterRetrieve(); 085 } 086 087 /** 088 * This method is called for refreshing the Agency before a save to display the full name in case the agency number was changed 089 * by hand just before the save. 090 */ 091 @Override 092 public void prepareForSave() { 093 refreshAward(false); 094 List<AwardProjectDirector> directors = getAward().getAwardProjectDirectors(); 095 if (directors.size() == 1) { 096 directors.get(0).setAwardPrimaryProjectDirectorIndicator(true); 097 } 098 List<AwardOrganization> organizations = getAward().getAwardOrganizations(); 099 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}