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}