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 }