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.HashMap;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.commons.lang.StringUtils;
026import org.apache.log4j.Logger;
027import org.kuali.ole.coa.businessobject.Account;
028import org.kuali.ole.coa.businessobject.SubAccount;
029import org.kuali.ole.coa.service.SubAccountTrickleDownInactivationService;
030import org.kuali.ole.sys.OLEKeyConstants;
031import org.kuali.ole.sys.OLEPropertyConstants;
032import org.kuali.rice.core.api.config.property.ConfigurationService;
033import org.kuali.rice.kns.maintenance.Maintainable;
034import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
035import org.kuali.rice.krad.bo.DocumentHeader;
036import org.kuali.rice.krad.bo.Note;
037import org.kuali.rice.krad.bo.PersistableBusinessObject;
038import org.kuali.rice.krad.dao.MaintenanceDocumentDao;
039import org.kuali.rice.krad.maintenance.MaintenanceLock;
040import org.kuali.rice.krad.service.DocumentHeaderService;
041import org.kuali.rice.krad.service.NoteService;
042import org.kuali.rice.krad.util.GlobalVariables;
043import org.kuali.rice.krad.util.ObjectUtils;
044import org.springframework.transaction.annotation.Transactional;
045
046@Transactional
047public class SubAccountTrickleDownInactivationServiceImpl implements SubAccountTrickleDownInactivationService {
048    private static final Logger LOG = Logger.getLogger(SubAccountTrickleDownInactivationServiceImpl.class);
049
050    protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
051    protected MaintenanceDocumentDao maintenanceDocumentDao;
052    protected NoteService noteService;
053    protected ConfigurationService kualiConfigurationService;
054    protected DocumentHeaderService documentHeaderService;
055    
056    /**
057     * Will generate Maintenance Locks for all (active or not) sub-accounts in the system related to the inactivated account using the sub-account
058     * maintainable registered for the sub-account maintenance document
059     * 
060     * This version of the method assumes that the sub-account maintainable only requires that the SubAccount BOClass, document number, and SubAccount
061     * instance only needs to be passed into it
062     * @see org.kuali.ole.gl.service.SubAccountTrickleDownInactivationService#generateTrickleDownMaintenanceLocks(org.kuali.ole.coa.businessobject.Account, java.lang.String)
063     */
064    public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Account inactivatedAccount, String documentNumber) {
065        inactivatedAccount.refreshReferenceObject(OLEPropertyConstants.SUB_ACCOUNTS);
066        List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
067        
068        Maintainable subAccountMaintainable;
069        try {
070            subAccountMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubAccount.class.getName()).newInstance();
071            subAccountMaintainable.setBoClass(SubAccount.class);
072            subAccountMaintainable.setDocumentNumber(documentNumber);
073        }
074        catch (Exception e) {
075            LOG.error("Unable to instantiate SubAccount Maintainable" , e);
076            throw new RuntimeException("Unable to instantiate SubAccount Maintainable" , e);
077        }
078        
079        if (ObjectUtils.isNotNull(inactivatedAccount.getSubAccounts()) && !inactivatedAccount.getSubAccounts().isEmpty()) {
080            for (Iterator<SubAccount> i = inactivatedAccount.getSubAccounts().iterator(); i.hasNext(); ) {
081                SubAccount subAccount = i.next();
082                
083                subAccountMaintainable.setBusinessObject(subAccount);
084                maintenanceLocks.addAll(subAccountMaintainable.generateMaintenanceLocks());
085            }
086        }
087        return maintenanceLocks;
088    }
089    
090    public void trickleDownInactivateSubAccounts(Account inactivatedAccount, String documentNumber) {
091        List<SubAccount> inactivatedSubAccounts = new ArrayList<SubAccount>();
092        Map<SubAccount, String> alreadyLockedSubAccounts = new HashMap<SubAccount, String>();
093        List<SubAccount> errorPersistingSubAccounts = new ArrayList<SubAccount>();
094        
095        Maintainable subAccountMaintainable;
096        try {
097            subAccountMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubAccount.class.getName()).newInstance();
098            subAccountMaintainable.setBoClass(SubAccount.class);
099            subAccountMaintainable.setDocumentNumber(documentNumber);
100        }
101        catch (Exception e) {
102            LOG.error("Unable to instantiate SubAccount Maintainable" , e);
103            throw new RuntimeException("Unable to instantiate SubAccount Maintainable" , e);
104        }
105        
106        inactivatedAccount.refreshReferenceObject(OLEPropertyConstants.SUB_ACCOUNTS);
107        if (ObjectUtils.isNotNull(inactivatedAccount.getSubAccounts()) && !inactivatedAccount.getSubAccounts().isEmpty()) {
108            for (Iterator<SubAccount> i = inactivatedAccount.getSubAccounts().iterator(); i.hasNext(); ) {
109                SubAccount subAccount = i.next();
110                if (subAccount.isActive()) {
111                    subAccountMaintainable.setBusinessObject(subAccount);
112                    List<MaintenanceLock> subAccountLocks = subAccountMaintainable.generateMaintenanceLocks();
113                    
114                    MaintenanceLock failedLock = verifyAllLocksFromThisDocument(subAccountLocks, documentNumber);
115                    if (failedLock != null) {
116                        // another document has locked this sub account, so we don't try to inactivate the account
117                        alreadyLockedSubAccounts.put(subAccount, failedLock.getDocumentNumber());
118                    }
119                    else {
120                        // no locks other than our own (but there may have been no locks at all), just go ahead and try to update
121                        subAccount.setActive(false);
122                        
123                        try {
124                            subAccountMaintainable.saveBusinessObject();
125                            inactivatedSubAccounts.add(subAccount);
126                        }
127                        catch (RuntimeException e) {
128                            LOG.error("Unable to trickle-down inactivate sub-account " + subAccount.toString(), e);
129                            errorPersistingSubAccounts.add(subAccount);
130                        }
131                    }
132                }
133            }
134            
135            addNotesToDocument(documentNumber, inactivatedSubAccounts, alreadyLockedSubAccounts, errorPersistingSubAccounts);
136        }
137    }
138
139    protected void addNotesToDocument(String documentNumber, List<SubAccount> inactivatedSubAccounts, Map<SubAccount, String> alreadyLockedSubAccounts, List<SubAccount> errorPersistingSubAccounts) {
140        if (inactivatedSubAccounts.isEmpty() && alreadyLockedSubAccounts.isEmpty() && errorPersistingSubAccounts.isEmpty()) {
141            // if we didn't try to inactivate any sub-accounts, then don't bother
142            return;
143        }
144        DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber);
145        Note newNote = new Note();
146        
147        addNotes(documentNumber, inactivatedSubAccounts, OLEKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION, noteParent, newNote);
148        addNotes(documentNumber, errorPersistingSubAccounts, OLEKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE, noteParent, newNote);
149        addMaintenanceLockedNotes(documentNumber, alreadyLockedSubAccounts, OLEKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION_RECORD_ALREADY_MAINTENANCE_LOCKED, noteParent, newNote);
150    }
151    
152    protected void addMaintenanceLockedNotes(String documentNumber, Map<SubAccount, String> lockedSubAccounts, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
153        for (Map.Entry<SubAccount, String> entry : lockedSubAccounts.entrySet()) {
154            try {
155                SubAccount subAccount = entry.getKey();
156                String subAccountString = subAccount.getChartOfAccountsCode() + " - " + subAccount.getAccountNumber() + " - " + subAccount.getSubAccountNumber();
157                if (StringUtils.isNotBlank(subAccountString)) {
158                    String noteTextTemplate = kualiConfigurationService.getPropertyValueAsString(messageKey);
159                    String noteText = MessageFormat.format(noteTextTemplate, subAccountString, entry.getValue());
160                    Note note = noteService.createNote(noteTemplate, noteParent, GlobalVariables.getUserSession().getPrincipalId());
161                    note.setNoteText(noteText);
162                    noteService.save(note);
163                }
164            }
165            catch (Exception e) {
166                LOG.error("Unable to create/save notes for document " + documentNumber, e);
167                throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e);
168            }
169        }
170    }
171
172    protected void addNotes(String documentNumber, List<SubAccount> listOfSubAccounts, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
173        for (int i = 0; i < listOfSubAccounts.size(); i += getNumSubAccountsPerNote()) {
174            try {
175                String subAccountString = createSubAccountChunk(listOfSubAccounts, i, i + getNumSubAccountsPerNote());
176                if (StringUtils.isNotBlank(subAccountString)) {
177                    String noteTextTemplate = kualiConfigurationService.getPropertyValueAsString(messageKey);
178                    String noteText = MessageFormat.format(noteTextTemplate, subAccountString);
179                    Note note = noteService.createNote(noteTemplate, noteParent, GlobalVariables.getUserSession().getPrincipalId());
180                    note.setNoteText(noteText);
181                    note.setNotePostedTimestampToCurrent();
182                    noteService.save(note);
183                }
184            }
185            catch (Exception e) {
186                LOG.error("Unable to create/save notes for document " + documentNumber, e);
187                throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e);
188            }
189        }
190    }
191    
192    protected String createSubAccountChunk(List<SubAccount> listOfSubAccounts, int startIndex, int endIndex) {
193        StringBuilder buf = new StringBuilder(); 
194        for (int i = startIndex; i < endIndex && i < listOfSubAccounts.size(); i++) {
195            SubAccount subAccount = listOfSubAccounts.get(i);
196            buf.append(subAccount.getChartOfAccountsCode()).append(" - ").append(subAccount.getAccountNumber()).append(" - ")
197                    .append(subAccount.getSubAccountNumber());
198            if (i + 1 < endIndex && i + 1 < listOfSubAccounts.size()) {
199                buf.append(", ");
200            }
201        }
202        return buf.toString();
203    }
204    
205    protected int getNumSubAccountsPerNote() {
206        return 20;
207    }
208    
209    protected MaintenanceLock verifyAllLocksFromThisDocument(List<MaintenanceLock> maintenanceLocks, String documentNumber) {
210        for (MaintenanceLock maintenanceLock : maintenanceLocks) {
211            String lockingDocNumber = maintenanceDocumentDao.getLockingDocumentNumber(maintenanceLock.getLockingRepresentation(), documentNumber);
212            if (StringUtils.isNotBlank(lockingDocNumber)) {
213                return maintenanceLock;
214            }
215        }
216        return null;
217    }
218
219    public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
220        this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
221    }
222
223
224
225    public void setMaintenanceDocumentDao(MaintenanceDocumentDao maintenanceDocumentDao) {
226        this.maintenanceDocumentDao = maintenanceDocumentDao;
227    }
228
229
230
231    public void setNoteService(NoteService noteService) {
232        this.noteService = noteService;
233    }
234
235    public void setConfigurationService(ConfigurationService kualiConfigurationService) {
236        this.kualiConfigurationService = kualiConfigurationService;
237    }
238
239    public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) {
240        this.documentHeaderService = documentHeaderService;
241    }
242}