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.Collection; 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.List; 024 import java.util.Map; 025 026 import org.apache.commons.lang.StringUtils; 027 import org.apache.log4j.Logger; 028 import org.kuali.kfs.coa.businessobject.Account; 029 import org.kuali.kfs.coa.businessobject.ObjectCode; 030 import org.kuali.kfs.coa.businessobject.SubObjectCode; 031 import org.kuali.kfs.coa.service.SubObjectTrickleDownInactivationService; 032 import org.kuali.kfs.sys.KFSKeyConstants; 033 import org.kuali.kfs.sys.KFSPropertyConstants; 034 import org.kuali.kfs.sys.service.UniversityDateService; 035 import org.kuali.rice.kns.bo.DocumentHeader; 036 import org.kuali.rice.kns.bo.Note; 037 import org.kuali.rice.kns.bo.PersistableBusinessObject; 038 import org.kuali.rice.kns.dao.MaintenanceDocumentDao; 039 import org.kuali.rice.kns.document.MaintenanceLock; 040 import org.kuali.rice.kns.maintenance.Maintainable; 041 import org.kuali.rice.kns.service.BusinessObjectService; 042 import org.kuali.rice.kns.service.DocumentHeaderService; 043 import org.kuali.rice.kns.service.KualiConfigurationService; 044 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 045 import org.kuali.rice.kns.service.NoteService; 046 import org.springframework.transaction.annotation.Transactional; 047 048 @Transactional 049 public class SubObjectTrickleDownInactivationServiceImpl implements SubObjectTrickleDownInactivationService { 050 private static final Logger LOG = Logger.getLogger(SubObjectTrickleDownInactivationServiceImpl.class); 051 052 protected BusinessObjectService businessObjectService; 053 protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; 054 protected MaintenanceDocumentDao maintenanceDocumentDao; 055 protected NoteService noteService; 056 protected KualiConfigurationService kualiConfigurationService; 057 protected UniversityDateService universityDateService; 058 protected DocumentHeaderService documentHeaderService; 059 060 public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Account inactivatedAccount, String documentNumber) { 061 Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedAccount); 062 List<MaintenanceLock> maintenanceLocks = generateTrickleDownMaintenanceLocks(subObjects, documentNumber); 063 return maintenanceLocks; 064 } 065 066 public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(ObjectCode inactivatedObjectCode, String documentNumber) { 067 Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedObjectCode); 068 List<MaintenanceLock> maintenanceLocks = generateTrickleDownMaintenanceLocks(subObjects, documentNumber); 069 return maintenanceLocks; 070 } 071 072 public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Collection<SubObjectCode> subObjects, String documentNumber) { 073 Maintainable subObjectMaintainable = getSubObjectMaintainable(documentNumber); 074 List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>(); 075 for (SubObjectCode subObjCd : subObjects) { 076 subObjectMaintainable.setBusinessObject(subObjCd); 077 maintenanceLocks.addAll(subObjectMaintainable.generateMaintenanceLocks()); 078 } 079 return maintenanceLocks; 080 } 081 082 protected class TrickleDownInactivationStatus { 083 public List<SubObjectCode> inactivatedSubObjCds; 084 public Map<SubObjectCode, String> alreadyLockedSubObjCds; 085 public List<SubObjectCode> errorPersistingSubObjCds; 086 087 public TrickleDownInactivationStatus() { 088 inactivatedSubObjCds = new ArrayList<SubObjectCode>(); 089 alreadyLockedSubObjCds = new HashMap<SubObjectCode, String>(); 090 errorPersistingSubObjCds = new ArrayList<SubObjectCode>(); 091 } 092 } 093 094 public void trickleDownInactivateSubObjects(Account inactivatedAccount, String documentNumber) { 095 Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedAccount); 096 TrickleDownInactivationStatus trickleDownInactivationStatus = trickleDownInactivate(subObjects, documentNumber); 097 addNotesToDocument(trickleDownInactivationStatus, documentNumber); 098 } 099 100 public void trickleDownInactivateSubObjects(ObjectCode inactivatedObject, String documentNumber) { 101 Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedObject); 102 TrickleDownInactivationStatus trickleDownInactivationStatus = trickleDownInactivate(subObjects, documentNumber); 103 addNotesToDocument(trickleDownInactivationStatus, documentNumber); 104 } 105 106 protected TrickleDownInactivationStatus trickleDownInactivate(Collection<SubObjectCode> subObjects, String documentNumber) { 107 TrickleDownInactivationStatus trickleDownInactivationStatus = new TrickleDownInactivationStatus(); 108 109 if (subObjects != null && !subObjects.isEmpty()) { 110 Maintainable subObjectMaintainable = getSubObjectMaintainable(documentNumber); 111 for (Iterator<SubObjectCode> i = subObjects.iterator(); i.hasNext(); ) { 112 SubObjectCode subObjCd = i.next(); 113 if (subObjCd.isActive()) { 114 subObjectMaintainable.setBusinessObject(subObjCd); 115 List<MaintenanceLock> subAccountLocks = subObjectMaintainable.generateMaintenanceLocks(); 116 117 MaintenanceLock failedLock = verifyAllLocksFromThisDocument(subAccountLocks, documentNumber); 118 if (failedLock != null) { 119 // another document has locked this sub account, so we don't try to inactivate the account 120 trickleDownInactivationStatus.alreadyLockedSubObjCds.put(subObjCd, failedLock.getDocumentNumber()); 121 } 122 else { 123 // no locks other than our own (but there may have been no locks at all), just go ahead and try to update 124 subObjCd.setActive(false); 125 126 try { 127 subObjectMaintainable.saveBusinessObject(); 128 trickleDownInactivationStatus.inactivatedSubObjCds.add(subObjCd); 129 } 130 catch (RuntimeException e) { 131 LOG.error("Unable to trickle-down inactivate sub-account " + subObjCd.toString(), e); 132 trickleDownInactivationStatus.errorPersistingSubObjCds.add(subObjCd); 133 } 134 } 135 } 136 } 137 } 138 139 return trickleDownInactivationStatus; 140 } 141 142 protected void addNotesToDocument(TrickleDownInactivationStatus trickleDownInactivationStatus, String documentNumber) { 143 if (trickleDownInactivationStatus.inactivatedSubObjCds.isEmpty() && trickleDownInactivationStatus.alreadyLockedSubObjCds.isEmpty() && trickleDownInactivationStatus.errorPersistingSubObjCds.isEmpty()) { 144 // if we didn't try to inactivate any sub-objects, then don't bother 145 return; 146 } 147 DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber); 148 Note newNote = new Note(); 149 150 addNotes(documentNumber, trickleDownInactivationStatus.inactivatedSubObjCds, KFSKeyConstants.SUB_OBJECT_TRICKLE_DOWN_INACTIVATION, noteParent, newNote); 151 addNotes(documentNumber, trickleDownInactivationStatus.errorPersistingSubObjCds, KFSKeyConstants.SUB_OBJECT_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE, noteParent, newNote); 152 addMaintenanceLockedNotes(documentNumber, trickleDownInactivationStatus.alreadyLockedSubObjCds, KFSKeyConstants.SUB_OBJECT_TRICKLE_DOWN_INACTIVATION_RECORD_ALREADY_MAINTENANCE_LOCKED, noteParent, newNote); 153 } 154 155 protected MaintenanceLock verifyAllLocksFromThisDocument(List<MaintenanceLock> maintenanceLocks, String documentNumber) { 156 for (MaintenanceLock maintenanceLock : maintenanceLocks) { 157 String lockingDocNumber = maintenanceDocumentDao.getLockingDocumentNumber(maintenanceLock.getLockingRepresentation(), documentNumber); 158 if (StringUtils.isNotBlank(lockingDocNumber)) { 159 return maintenanceLock; 160 } 161 } 162 return null; 163 } 164 165 protected Maintainable getSubObjectMaintainable(String documentNumber) { 166 Maintainable subObjectMaintainable; 167 try { 168 subObjectMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubObjectCode.class.getName()).newInstance(); 169 subObjectMaintainable.setBoClass(SubObjectCode.class); 170 subObjectMaintainable.setDocumentNumber(documentNumber); 171 } 172 catch (Exception e) { 173 LOG.error("Unable to instantiate SubObject Maintainable" , e); 174 throw new RuntimeException("Unable to instantiate SubObject Maintainable" , e); 175 } 176 return subObjectMaintainable; 177 } 178 179 protected Collection<SubObjectCode> getAssociatedSubObjects(Account account) { 180 Map<String, Object> fieldValues = new HashMap<String, Object>(); 181 fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, universityDateService.getCurrentFiscalYear()); 182 fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, account.getChartOfAccountsCode()); 183 fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, account.getAccountNumber()); 184 return businessObjectService.findMatching(SubObjectCode.class, fieldValues); 185 } 186 187 protected Collection<SubObjectCode> getAssociatedSubObjects(ObjectCode objectCode) { 188 Map<String, Object> fieldValues = new HashMap<String, Object>(); 189 fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, objectCode.getUniversityFiscalYear()); 190 fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, objectCode.getChartOfAccountsCode()); 191 fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, objectCode.getFinancialObjectCode()); 192 return businessObjectService.findMatching(SubObjectCode.class, fieldValues); 193 } 194 195 protected void addNotes(String documentNumber, List<SubObjectCode> listOfSubObjects, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) { 196 for (int i = 0; i < listOfSubObjects.size(); i += getNumSubObjectsPerNote()) { 197 try { 198 String subAccountString = createSubObjectChunk(listOfSubObjects, i, i + getNumSubObjectsPerNote()); 199 if (StringUtils.isNotBlank(subAccountString)) { 200 String noteTextTemplate = kualiConfigurationService.getPropertyString(messageKey); 201 String noteText = MessageFormat.format(noteTextTemplate, subAccountString); 202 Note note = noteService.createNote(noteTemplate, noteParent); 203 note.setNoteText(noteText); 204 noteService.save(note); 205 } 206 } 207 catch (Exception e) { 208 LOG.error("Unable to create/save notes for document " + documentNumber, e); 209 throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e); 210 } 211 } 212 } 213 214 protected void addMaintenanceLockedNotes(String documentNumber, Map<SubObjectCode, String> lockedSubObjects, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) { 215 for (Map.Entry<SubObjectCode, String> entry : lockedSubObjects.entrySet()) { 216 try { 217 SubObjectCode subObjCd = entry.getKey(); 218 String subObjectString = subObjCd.getUniversityFiscalYear() + " - " + subObjCd.getChartOfAccountsCode() + " - " + subObjCd.getAccountNumber() + " - " + subObjCd.getFinancialObjectCode() + " - " + subObjCd.getFinancialSubObjectCode(); 219 if (StringUtils.isNotBlank(subObjectString)) { 220 String noteTextTemplate = kualiConfigurationService.getPropertyString(messageKey); 221 String noteText = MessageFormat.format(noteTextTemplate, subObjectString, entry.getValue()); 222 Note note = noteService.createNote(noteTemplate, noteParent); 223 note.setNoteText(noteText); 224 noteService.save(note); 225 } 226 } 227 catch (Exception e) { 228 LOG.error("Unable to create/save notes for document " + documentNumber, e); 229 throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e); 230 } 231 } 232 } 233 234 protected String createSubObjectChunk(List<SubObjectCode> listOfSubObjects, int startIndex, int endIndex) { 235 StringBuilder buf = new StringBuilder(); 236 for (int i = startIndex; i < endIndex && i < listOfSubObjects.size(); i++) { 237 SubObjectCode subObjCd = listOfSubObjects.get(i); 238 buf.append(subObjCd.getUniversityFiscalYear()).append(" - ").append(subObjCd.getChartOfAccountsCode()).append(" - ") 239 .append(subObjCd.getAccountNumber()).append(" - ").append(subObjCd.getFinancialObjectCode()) 240 .append(" - ").append(subObjCd.getFinancialSubObjectCode()); 241 if (i + 1 < endIndex && i + 1 < listOfSubObjects.size()) { 242 buf.append(", "); 243 } 244 } 245 return buf.toString(); 246 } 247 248 protected int getNumSubObjectsPerNote() { 249 return 20; 250 } 251 252 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 253 this.businessObjectService = businessObjectService; 254 } 255 256 public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) { 257 this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService; 258 } 259 260 public void setMaintenanceDocumentDao(MaintenanceDocumentDao maintenanceDocumentDao) { 261 this.maintenanceDocumentDao = maintenanceDocumentDao; 262 } 263 264 public void setNoteService(NoteService noteService) { 265 this.noteService = noteService; 266 } 267 268 public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) { 269 this.kualiConfigurationService = kualiConfigurationService; 270 } 271 272 public void setUniversityDateService(UniversityDateService universityDateService) { 273 this.universityDateService = universityDateService; 274 } 275 276 public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) { 277 this.documentHeaderService = documentHeaderService; 278 } 279 }