View Javadoc
1   /*
2    * Copyright 2008 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.coa.service.impl;
17  
18  import java.text.MessageFormat;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.log4j.Logger;
28  import org.kuali.ole.coa.businessobject.Account;
29  import org.kuali.ole.coa.businessobject.ObjectCode;
30  import org.kuali.ole.coa.businessobject.SubObjectCode;
31  import org.kuali.ole.coa.service.SubObjectTrickleDownInactivationService;
32  import org.kuali.ole.sys.OLEKeyConstants;
33  import org.kuali.ole.sys.OLEPropertyConstants;
34  import org.kuali.ole.sys.service.UniversityDateService;
35  import org.kuali.rice.core.api.config.property.ConfigurationService;
36  import org.kuali.rice.kns.maintenance.Maintainable;
37  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
38  import org.kuali.rice.krad.bo.DocumentHeader;
39  import org.kuali.rice.krad.bo.Note;
40  import org.kuali.rice.krad.bo.PersistableBusinessObject;
41  import org.kuali.rice.krad.dao.MaintenanceDocumentDao;
42  import org.kuali.rice.krad.maintenance.MaintenanceLock;
43  import org.kuali.rice.krad.service.BusinessObjectService;
44  import org.kuali.rice.krad.service.DocumentHeaderService;
45  import org.kuali.rice.krad.service.NoteService;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  import org.springframework.transaction.annotation.Transactional;
48  
49  @Transactional
50  public class SubObjectTrickleDownInactivationServiceImpl implements SubObjectTrickleDownInactivationService {
51      
52      //MSU Contribution DTT-3791 OLEMI-8645 OLECNTRB-976
53      private static final int NO_OF_SUB_OBJECTS_PER_NOTE = 15;
54  
55      private static final Logger LOG = Logger.getLogger(SubObjectTrickleDownInactivationServiceImpl.class);
56      
57      protected BusinessObjectService businessObjectService;
58      protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
59      protected MaintenanceDocumentDao maintenanceDocumentDao;
60      protected NoteService noteService;
61      protected ConfigurationService kualiConfigurationService;
62      protected UniversityDateService universityDateService;
63      protected DocumentHeaderService documentHeaderService;
64      
65      public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Account inactivatedAccount, String documentNumber) {
66          Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedAccount);
67          List<MaintenanceLock> maintenanceLocks = generateTrickleDownMaintenanceLocks(subObjects, documentNumber);
68          return maintenanceLocks;
69      }
70  
71      public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(ObjectCode inactivatedObjectCode, String documentNumber) {
72          Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedObjectCode);
73          List<MaintenanceLock> maintenanceLocks = generateTrickleDownMaintenanceLocks(subObjects, documentNumber);
74          return maintenanceLocks;
75      }
76  
77      public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Collection<SubObjectCode> subObjects, String documentNumber) {
78          Maintainable subObjectMaintainable = getSubObjectMaintainable(documentNumber);
79          List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
80          for (SubObjectCode subObjCd : subObjects) {
81              subObjectMaintainable.setBusinessObject(subObjCd);
82              maintenanceLocks.addAll(subObjectMaintainable.generateMaintenanceLocks());
83          }
84          return maintenanceLocks;
85      }
86      
87      protected class TrickleDownInactivationStatus {
88          public List<SubObjectCode> inactivatedSubObjCds;
89          public Map<SubObjectCode, String> alreadyLockedSubObjCds;
90          public List<SubObjectCode> errorPersistingSubObjCds;
91          
92          public TrickleDownInactivationStatus() {
93              inactivatedSubObjCds = new ArrayList<SubObjectCode>();
94              alreadyLockedSubObjCds = new HashMap<SubObjectCode, String>();
95              errorPersistingSubObjCds = new ArrayList<SubObjectCode>();
96          }
97      }
98      
99      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 }