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    }