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