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 }