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.service.impl;
017    
018    import java.text.MessageFormat;
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.apache.log4j.Logger;
027    import org.kuali.kfs.coa.businessobject.Account;
028    import org.kuali.kfs.coa.businessobject.SubAccount;
029    import org.kuali.kfs.coa.service.SubAccountTrickleDownInactivationService;
030    import org.kuali.kfs.sys.KFSKeyConstants;
031    import org.kuali.kfs.sys.KFSPropertyConstants;
032    import org.kuali.rice.kns.bo.DocumentHeader;
033    import org.kuali.rice.kns.bo.Note;
034    import org.kuali.rice.kns.bo.PersistableBusinessObject;
035    import org.kuali.rice.kns.dao.MaintenanceDocumentDao;
036    import org.kuali.rice.kns.document.MaintenanceLock;
037    import org.kuali.rice.kns.maintenance.Maintainable;
038    import org.kuali.rice.kns.service.DocumentHeaderService;
039    import org.kuali.rice.kns.service.KualiConfigurationService;
040    import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
041    import org.kuali.rice.kns.service.NoteService;
042    import org.kuali.rice.kns.util.ObjectUtils;
043    import org.springframework.transaction.annotation.Transactional;
044    
045    @Transactional
046    public class SubAccountTrickleDownInactivationServiceImpl implements SubAccountTrickleDownInactivationService {
047        private static final Logger LOG = Logger.getLogger(SubAccountTrickleDownInactivationServiceImpl.class);
048    
049        protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
050        protected MaintenanceDocumentDao maintenanceDocumentDao;
051        protected NoteService noteService;
052        protected KualiConfigurationService kualiConfigurationService;
053        protected DocumentHeaderService documentHeaderService;
054        
055        /**
056         * Will generate Maintenance Locks for all (active or not) sub-accounts in the system related to the inactivated account using the sub-account
057         * maintainable registered for the sub-account maintenance document
058         * 
059         * This version of the method assumes that the sub-account maintainable only requires that the SubAccount BOClass, document number, and SubAccount
060         * instance only needs to be passed into it
061         * @see org.kuali.kfs.gl.service.SubAccountTrickleDownInactivationService#generateTrickleDownMaintenanceLocks(org.kuali.kfs.coa.businessobject.Account, java.lang.String)
062         */
063        public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Account inactivatedAccount, String documentNumber) {
064            inactivatedAccount.refreshReferenceObject(KFSPropertyConstants.SUB_ACCOUNTS);
065            List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
066            
067            Maintainable subAccountMaintainable;
068            try {
069                subAccountMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubAccount.class.getName()).newInstance();
070                subAccountMaintainable.setBoClass(SubAccount.class);
071                subAccountMaintainable.setDocumentNumber(documentNumber);
072            }
073            catch (Exception e) {
074                LOG.error("Unable to instantiate SubAccount Maintainable" , e);
075                throw new RuntimeException("Unable to instantiate SubAccount Maintainable" , e);
076            }
077            
078            if (ObjectUtils.isNotNull(inactivatedAccount.getSubAccounts()) && !inactivatedAccount.getSubAccounts().isEmpty()) {
079                for (Iterator<SubAccount> i = inactivatedAccount.getSubAccounts().iterator(); i.hasNext(); ) {
080                    SubAccount subAccount = i.next();
081                    
082                    subAccountMaintainable.setBusinessObject(subAccount);
083                    maintenanceLocks.addAll(subAccountMaintainable.generateMaintenanceLocks());
084                }
085            }
086            return maintenanceLocks;
087        }
088        
089        public void trickleDownInactivateSubAccounts(Account inactivatedAccount, String documentNumber) {
090            List<SubAccount> inactivatedSubAccounts = new ArrayList<SubAccount>();
091            Map<SubAccount, String> alreadyLockedSubAccounts = new HashMap<SubAccount, String>();
092            List<SubAccount> errorPersistingSubAccounts = new ArrayList<SubAccount>();
093            
094            Maintainable subAccountMaintainable;
095            try {
096                subAccountMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubAccount.class.getName()).newInstance();
097                subAccountMaintainable.setBoClass(SubAccount.class);
098                subAccountMaintainable.setDocumentNumber(documentNumber);
099            }
100            catch (Exception e) {
101                LOG.error("Unable to instantiate SubAccount Maintainable" , e);
102                throw new RuntimeException("Unable to instantiate SubAccount Maintainable" , e);
103            }
104            
105            inactivatedAccount.refreshReferenceObject(KFSPropertyConstants.SUB_ACCOUNTS);
106            if (ObjectUtils.isNotNull(inactivatedAccount.getSubAccounts()) && !inactivatedAccount.getSubAccounts().isEmpty()) {
107                for (Iterator<SubAccount> i = inactivatedAccount.getSubAccounts().iterator(); i.hasNext(); ) {
108                    SubAccount subAccount = i.next();
109                    if (subAccount.isActive()) {
110                        subAccountMaintainable.setBusinessObject(subAccount);
111                        List<MaintenanceLock> subAccountLocks = subAccountMaintainable.generateMaintenanceLocks();
112                        
113                        MaintenanceLock failedLock = verifyAllLocksFromThisDocument(subAccountLocks, documentNumber);
114                        if (failedLock != null) {
115                            // another document has locked this sub account, so we don't try to inactivate the account
116                            alreadyLockedSubAccounts.put(subAccount, failedLock.getDocumentNumber());
117                        }
118                        else {
119                            // no locks other than our own (but there may have been no locks at all), just go ahead and try to update
120                            subAccount.setActive(false);
121                            
122                            try {
123                                subAccountMaintainable.saveBusinessObject();
124                                inactivatedSubAccounts.add(subAccount);
125                            }
126                            catch (RuntimeException e) {
127                                LOG.error("Unable to trickle-down inactivate sub-account " + subAccount.toString(), e);
128                                errorPersistingSubAccounts.add(subAccount);
129                            }
130                        }
131                    }
132                }
133                
134                addNotesToDocument(documentNumber, inactivatedSubAccounts, alreadyLockedSubAccounts, errorPersistingSubAccounts);
135            }
136        }
137    
138        protected void addNotesToDocument(String documentNumber, List<SubAccount> inactivatedSubAccounts, Map<SubAccount, String> alreadyLockedSubAccounts, List<SubAccount> errorPersistingSubAccounts) {
139            if (inactivatedSubAccounts.isEmpty() && alreadyLockedSubAccounts.isEmpty() && errorPersistingSubAccounts.isEmpty()) {
140                // if we didn't try to inactivate any sub-accounts, then don't bother
141                return;
142            }
143            DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber);
144            Note newNote = new Note();
145            
146            addNotes(documentNumber, inactivatedSubAccounts, KFSKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION, noteParent, newNote);
147            addNotes(documentNumber, errorPersistingSubAccounts, KFSKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE, noteParent, newNote);
148            addMaintenanceLockedNotes(documentNumber, alreadyLockedSubAccounts, KFSKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION_RECORD_ALREADY_MAINTENANCE_LOCKED, noteParent, newNote);
149        }
150        
151        protected void addMaintenanceLockedNotes(String documentNumber, Map<SubAccount, String> lockedSubAccounts, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
152            for (Map.Entry<SubAccount, String> entry : lockedSubAccounts.entrySet()) {
153                try {
154                    SubAccount subAccount = entry.getKey();
155                    String subAccountString = subAccount.getChartOfAccountsCode() + " - " + subAccount.getAccountNumber() + " - " + subAccount.getSubAccountNumber();
156                    if (StringUtils.isNotBlank(subAccountString)) {
157                        String noteTextTemplate = kualiConfigurationService.getPropertyString(messageKey);
158                        String noteText = MessageFormat.format(noteTextTemplate, subAccountString, entry.getValue());
159                        Note note = noteService.createNote(noteTemplate, noteParent);
160                        note.setNoteText(noteText);
161                        noteService.save(note);
162                    }
163                }
164                catch (Exception e) {
165                    LOG.error("Unable to create/save notes for document " + documentNumber, e);
166                    throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e);
167                }
168            }
169        }
170    
171        protected void addNotes(String documentNumber, List<SubAccount> listOfSubAccounts, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
172            for (int i = 0; i < listOfSubAccounts.size(); i += getNumSubAccountsPerNote()) {
173                try {
174                    String subAccountString = createSubAccountChunk(listOfSubAccounts, i, i + getNumSubAccountsPerNote());
175                    if (StringUtils.isNotBlank(subAccountString)) {
176                        String noteTextTemplate = kualiConfigurationService.getPropertyString(messageKey);
177                        String noteText = MessageFormat.format(noteTextTemplate, subAccountString);
178                        Note note = noteService.createNote(noteTemplate, noteParent);
179                        note.setNoteText(noteText);
180                        noteService.save(note);
181                    }
182                }
183                catch (Exception e) {
184                    LOG.error("Unable to create/save notes for document " + documentNumber, e);
185                    throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e);
186                }
187            }
188        }
189        
190        protected String createSubAccountChunk(List<SubAccount> listOfSubAccounts, int startIndex, int endIndex) {
191            StringBuilder buf = new StringBuilder(); 
192            for (int i = startIndex; i < endIndex && i < listOfSubAccounts.size(); i++) {
193                SubAccount subAccount = listOfSubAccounts.get(i);
194                buf.append(subAccount.getChartOfAccountsCode()).append(" - ").append(subAccount.getAccountNumber()).append(" - ")
195                        .append(subAccount.getSubAccountNumber());
196                if (i + 1 < endIndex && i + 1 < listOfSubAccounts.size()) {
197                    buf.append(", ");
198                }
199            }
200            return buf.toString();
201        }
202        
203        protected int getNumSubAccountsPerNote() {
204            return 20;
205        }
206        
207        protected MaintenanceLock verifyAllLocksFromThisDocument(List<MaintenanceLock> maintenanceLocks, String documentNumber) {
208            for (MaintenanceLock maintenanceLock : maintenanceLocks) {
209                String lockingDocNumber = maintenanceDocumentDao.getLockingDocumentNumber(maintenanceLock.getLockingRepresentation(), documentNumber);
210                if (StringUtils.isNotBlank(lockingDocNumber)) {
211                    return maintenanceLock;
212                }
213            }
214            return null;
215        }
216    
217        public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
218            this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
219        }
220    
221    
222    
223        public void setMaintenanceDocumentDao(MaintenanceDocumentDao maintenanceDocumentDao) {
224            this.maintenanceDocumentDao = maintenanceDocumentDao;
225        }
226    
227    
228    
229        public void setNoteService(NoteService noteService) {
230            this.noteService = noteService;
231        }
232    
233        public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
234            this.kualiConfigurationService = kualiConfigurationService;
235        }
236    
237        public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) {
238            this.documentHeaderService = documentHeaderService;
239        }
240    }