001    /*
002     * Copyright 2011 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     */
016    package org.kuali.kfs.coa.document;
017    
018    import java.sql.Date;
019    import java.util.Iterator;
020    import java.util.List;
021    import java.util.Map;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.apache.log4j.Logger;
025    import org.kuali.kfs.coa.businessobject.Account;
026    import org.kuali.kfs.coa.service.AccountPersistenceStructureService;
027    import org.kuali.kfs.coa.service.AccountService;
028    import org.kuali.kfs.coa.service.SubAccountTrickleDownInactivationService;
029    import org.kuali.kfs.coa.service.SubObjectTrickleDownInactivationService;
030    import org.kuali.kfs.sys.KFSPropertyConstants;
031    import org.kuali.kfs.sys.context.SpringContext;
032    import org.kuali.kfs.sys.document.FinancialSystemMaintainable;
033    import org.kuali.rice.kns.bo.PersistableBusinessObject;
034    import org.kuali.rice.kns.document.MaintenanceDocument;
035    import org.kuali.rice.kns.document.MaintenanceLock;
036    import org.kuali.rice.kns.service.DateTimeService;
037    import org.kuali.rice.kns.util.KNSConstants;
038    import org.kuali.rice.kns.util.ObjectUtils;
039    
040    /**
041     * This class overrides the saveBusinessObject() method which is called during post process from the KualiPostProcessor so that it
042     * can automatically deactivate the Sub-Accounts related to the account It also overrides the processAfterCopy so that it sets
043     * specific fields that shouldn't be copied to default values {@link KualiPostProcessor}
044     */
045    public class KualiAccountMaintainableImpl extends FinancialSystemMaintainable {
046        private static final Logger LOG = Logger.getLogger(KualiAccountMaintainableImpl.class);
047        private static final String ACCOUNT_GUIDE_LINE_PROPERTY = "accountGuideline";
048        
049        /**
050         * Automatically deactivates {@link SubAccount}s after saving the {@link Account}
051         * 
052         * @see org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject()
053         */
054        @Override
055        public void saveBusinessObject() {
056            boolean isClosingAccount = isClosingAccount();
057    
058            // make sure we save account first
059            super.saveBusinessObject();
060    
061            // if we're closing the account, then rely on the trickle-down inactivation services to trickle-down inactivate the
062            // sub-accounts
063            if (isClosingAccount) {
064                SpringContext.getBean(SubAccountTrickleDownInactivationService.class).trickleDownInactivateSubAccounts((Account) getBusinessObject(), documentNumber);
065                SpringContext.getBean(SubObjectTrickleDownInactivationService.class).trickleDownInactivateSubObjects((Account) getBusinessObject(), documentNumber);
066            }
067        }
068    
069        /**
070         * After a copy is done set specific fields on {@link Account} to default values
071         * 
072         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterCopy()
073         */
074        @Override
075        public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
076            Account account = (Account) this.getBusinessObject();
077            account.setAccountCreateDate(null); // account's pre-rules will fill this field in
078            account.setAccountEffectiveDate(new Date(SpringContext.getBean(DateTimeService.class).getCurrentDate().getTime()));
079            account.setActive(true);
080            super.processAfterCopy(document, parameters);
081        }
082        
083     
084        @Override
085        public List<MaintenanceLock> generateMaintenanceLocks() {
086            List<MaintenanceLock> maintenanceLocks = super.generateMaintenanceLocks();
087            boolean isClosingAccount = false;
088    
089            if (isClosingAccount()) {
090                maintenanceLocks.addAll(SpringContext.getBean(SubAccountTrickleDownInactivationService.class).generateTrickleDownMaintenanceLocks((Account) getBusinessObject(), documentNumber));
091                maintenanceLocks.addAll(SpringContext.getBean(SubObjectTrickleDownInactivationService.class).generateTrickleDownMaintenanceLocks((Account) getBusinessObject(), documentNumber));
092            }
093            return maintenanceLocks;
094        }
095    
096        protected Account retrieveExistingAccountFromDB() {
097            Account newAccount = (Account) getBusinessObject();
098             Account oldAccount = SpringContext.getBean(AccountService.class).getByPrimaryId(newAccount.getChartOfAccountsCode(), newAccount.getAccountNumber());
099            return oldAccount;
100        }
101    
102        protected boolean isClosingAccount() {
103            // the account has to be closed on the new side when editing in order for it to be possible that we are closing the account
104            if (KNSConstants.MAINTENANCE_EDIT_ACTION.equals(getMaintenanceAction()) && !((Account) getBusinessObject()).isActive()) {
105                Account existingAccountFromDB = retrieveExistingAccountFromDB();
106                if (ObjectUtils.isNotNull(existingAccountFromDB)) {
107                    // now see if the original account was not closed, in which case, we are closing the account
108                    if (existingAccountFromDB.isActive()) {
109                        return true;
110                    }
111                }
112            }
113            return false;
114        }
115    
116        /**
117         * Determines who should be FYI'd as the account supervisor for the routing of the account maintenance document. If there is an
118         * existing account, it uses the account supervisor from that; otherwise, it uses the account supervisor from the business
119         * object of this maintainable
120         * 
121         * @return an appropriate account supervisor to FYI during account maintenance document routing
122         */
123        public String getRoutingAccountsSupervisorySystemsIdentifier() {
124            final Account existingAccountFromDB = retrieveExistingAccountFromDB();
125            if (ObjectUtils.isNull(existingAccountFromDB)) {
126                return ((Account) getBusinessObject()).getAccountsSupervisorySystemsIdentifier();
127            }
128            return existingAccountFromDB.getAccountsSupervisorySystemsIdentifier();
129        }
130    
131        /**
132         * Had to override this method because account guideline data was lost when copied and then a lookup is performed
133         * 
134         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map,
135         *      org.kuali.rice.kns.document.MaintenanceDocument)
136         */
137        @Override
138        public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
139            super.refresh(refreshCaller, fieldValues, document);
140            Account newAcct = (Account) document.getNewMaintainableObject().getBusinessObject();
141            Account oldAcct = (Account) document.getOldMaintainableObject().getBusinessObject();
142            if (KNSConstants.MAINTENANCE_COPY_ACTION.equals(document.getNewMaintainableObject().getMaintenanceAction())) {
143                if (ObjectUtils.isNull(newAcct.getAccountGuideline())) {
144                    newAcct.setAccountGuideline(oldAcct.getAccountGuideline());
145                }
146            }
147    
148        }
149    
150        @Override
151        protected void refreshReferences(String referencesToRefresh) {        
152            //make call to super
153            super.refreshReferences( removeReferenceFromString(referencesToRefresh, ACCOUNT_GUIDE_LINE_PROPERTY) );
154        }
155        
156        /**
157         * Removes a named reference from a referencesToRefresh string
158         */
159        protected String removeReferenceFromString(String referencesToRefresh, String referenceToRemove){
160            String newReference = referencesToRefresh;
161            
162            if(ObjectUtils.isNotNull(newReference)){
163                int index = newReference.indexOf(referenceToRemove);        
164                if(index != -1){  
165                    //remove from beginning
166                    if(index == 0){
167                        
168                        String suffix = "";
169                        //add comma at end since there is more after this word
170                        if(newReference.length() != referenceToRemove.length()){
171                            suffix = ",";
172                        }                    
173                        newReference = referencesToRefresh.replaceAll(ACCOUNT_GUIDE_LINE_PROPERTY + suffix, "");
174                        
175                    }else{
176                        //removing from middle to end... either way, comma will be in front
177                        newReference = referencesToRefresh.replaceAll("," + ACCOUNT_GUIDE_LINE_PROPERTY, "");
178                    }
179                }
180            }
181            
182            return newReference;
183        }
184        
185        /**
186         * @see org.kuali.kfs.sys.document.FinancialSystemMaintainable#populateChartOfAccountsCodeFields()
187         * 
188         * Special treatment is needed when a new Account is created, the chartCode-accountNumber fields in the document can use the new account 
189         * that's being created; in which case chart code shall be populated from the PK chart code in the document instead of retrieving it from DB
190         * using the account number, as the new account doesn't exist in the DB yet.
191         */
192        @Override
193        protected void populateChartOfAccountsCodeFields() {
194            // super method is not called because the logic there wouldn't apply here        
195            AccountService acctService = SpringContext.getBean(AccountService.class);        
196            AccountPersistenceStructureService apsService = SpringContext.getBean(AccountPersistenceStructureService.class);
197            PersistableBusinessObject bo = getBusinessObject();        
198            Iterator<Map.Entry<String, String>> chartAccountPairs = apsService.listChartCodeAccountNumberPairs(bo).entrySet().iterator();        
199     
200            // all reference accounts could possibly use the same new accounting being created in the current document 
201            while (chartAccountPairs.hasNext()) {
202                Map.Entry<String, String> entry = (Map.Entry<String, String>)chartAccountPairs.next();
203                String coaCodeName = entry.getKey();            
204                String acctNumName = entry.getValue(); 
205                String accountNumber = (String)ObjectUtils.getPropertyValue(bo, acctNumName);
206                String coaCode = null;
207                String coaCodePK = (String)ObjectUtils.getPropertyValue(bo, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
208                String accountNumberPK = (String)ObjectUtils.getPropertyValue(bo, KFSPropertyConstants.ACCOUNT_NUMBER);
209                
210                // if reference account number is same as the primary key accountNumber, copy the primary key chart code to reference chart Code
211                if (StringUtils.equalsIgnoreCase(accountNumber, accountNumberPK)) {
212                    coaCode = coaCodePK;
213                }
214                // otherwise retrieve chart code from account as usual
215                else {
216                    Account account = acctService.getUniqueAccountForAccountNumber(accountNumber);            
217                    if (ObjectUtils.isNotNull(account)) {
218                        coaCode = account.getChartOfAccountsCode();
219                    }
220                }
221                
222                try {
223                    ObjectUtils.setObjectProperty(bo, coaCodeName, coaCode); 
224                }
225                catch (Exception e) {
226                    LOG.error("Error in setting property value for " + coaCodeName);
227                }
228            }
229        }    
230    
231    }