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}