/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.coa.service.impl;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.SubAccount;
import org.kuali.kfs.coa.service.SubAccountTrickleDownInactivationService;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.kns.maintenance.Maintainable;
import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
import org.kuali.rice.krad.bo.DocumentHeader;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.dao.MaintenanceDocumentDao;
import org.kuali.rice.krad.maintenance.MaintenanceLock;
import org.kuali.rice.krad.service.DocumentHeaderService;
import org.kuali.rice.krad.service.NoteService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class SubAccountTrickleDownInactivationServiceImpl implements SubAccountTrickleDownInactivationService {
private static final Logger LOG = Logger.getLogger(SubAccountTrickleDownInactivationServiceImpl.class);
protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
protected MaintenanceDocumentDao maintenanceDocumentDao;
protected NoteService noteService;
protected ConfigurationService kualiConfigurationService;
protected DocumentHeaderService documentHeaderService;
/**
* Will generate Maintenance Locks for all (active or not) sub-accounts in the system related to the inactivated account using the sub-account
* maintainable registered for the sub-account maintenance document
*
* This version of the method assumes that the sub-account maintainable only requires that the SubAccount BOClass, document number, and SubAccount
* instance only needs to be passed into it
* @see org.kuali.kfs.gl.service.SubAccountTrickleDownInactivationService#generateTrickleDownMaintenanceLocks(org.kuali.kfs.coa.businessobject.Account, java.lang.String)
*/
public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Account inactivatedAccount, String documentNumber) {
inactivatedAccount.refreshReferenceObject(KFSPropertyConstants.SUB_ACCOUNTS);
List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
Maintainable subAccountMaintainable;
try {
subAccountMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubAccount.class.getName()).newInstance();
subAccountMaintainable.setBoClass(SubAccount.class);
subAccountMaintainable.setDocumentNumber(documentNumber);
}
catch (Exception e) {
LOG.error("Unable to instantiate SubAccount Maintainable" , e);
throw new RuntimeException("Unable to instantiate SubAccount Maintainable" , e);
}
if (ObjectUtils.isNotNull(inactivatedAccount.getSubAccounts()) && !inactivatedAccount.getSubAccounts().isEmpty()) {
for (Iterator<SubAccount> i = inactivatedAccount.getSubAccounts().iterator(); i.hasNext(); ) {
SubAccount subAccount = i.next();
subAccountMaintainable.setBusinessObject(subAccount);
maintenanceLocks.addAll(subAccountMaintainable.generateMaintenanceLocks());
}
}
return maintenanceLocks;
}
public void trickleDownInactivateSubAccounts(Account inactivatedAccount, String documentNumber) {
List<SubAccount> inactivatedSubAccounts = new ArrayList<SubAccount>();
Map<SubAccount, String> alreadyLockedSubAccounts = new HashMap<SubAccount, String>();
List<SubAccount> errorPersistingSubAccounts = new ArrayList<SubAccount>();
Maintainable subAccountMaintainable;
try {
subAccountMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubAccount.class.getName()).newInstance();
subAccountMaintainable.setBoClass(SubAccount.class);
subAccountMaintainable.setDocumentNumber(documentNumber);
}
catch (Exception e) {
LOG.error("Unable to instantiate SubAccount Maintainable" , e);
throw new RuntimeException("Unable to instantiate SubAccount Maintainable" , e);
}
inactivatedAccount.refreshReferenceObject(KFSPropertyConstants.SUB_ACCOUNTS);
if (ObjectUtils.isNotNull(inactivatedAccount.getSubAccounts()) && !inactivatedAccount.getSubAccounts().isEmpty()) {
for (Iterator<SubAccount> i = inactivatedAccount.getSubAccounts().iterator(); i.hasNext(); ) {
SubAccount subAccount = i.next();
if (subAccount.isActive()) {
subAccountMaintainable.setBusinessObject(subAccount);
List<MaintenanceLock> subAccountLocks = subAccountMaintainable.generateMaintenanceLocks();
MaintenanceLock failedLock = verifyAllLocksFromThisDocument(subAccountLocks, documentNumber);
if (failedLock != null) {
// another document has locked this sub account, so we don't try to inactivate the account
alreadyLockedSubAccounts.put(subAccount, failedLock.getDocumentNumber());
}
else {
// no locks other than our own (but there may have been no locks at all), just go ahead and try to update
subAccount.setActive(false);
try {
subAccountMaintainable.saveBusinessObject();
inactivatedSubAccounts.add(subAccount);
}
catch (RuntimeException e) {
LOG.error("Unable to trickle-down inactivate sub-account " + subAccount.toString(), e);
errorPersistingSubAccounts.add(subAccount);
}
}
}
}
addNotesToDocument(documentNumber, inactivatedSubAccounts, alreadyLockedSubAccounts, errorPersistingSubAccounts);
}
}
protected void addNotesToDocument(String documentNumber, List<SubAccount> inactivatedSubAccounts, Map<SubAccount, String> alreadyLockedSubAccounts, List<SubAccount> errorPersistingSubAccounts) {
if (inactivatedSubAccounts.isEmpty() && alreadyLockedSubAccounts.isEmpty() && errorPersistingSubAccounts.isEmpty()) {
// if we didn't try to inactivate any sub-accounts, then don't bother
return;
}
DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber);
Note newNote = new Note();
addNotes(documentNumber, inactivatedSubAccounts, KFSKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION, noteParent, newNote);
addNotes(documentNumber, errorPersistingSubAccounts, KFSKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE, noteParent, newNote);
addMaintenanceLockedNotes(documentNumber, alreadyLockedSubAccounts, KFSKeyConstants.SUB_ACCOUNT_TRICKLE_DOWN_INACTIVATION_RECORD_ALREADY_MAINTENANCE_LOCKED, noteParent, newNote);
}
protected void addMaintenanceLockedNotes(String documentNumber, Map<SubAccount, String> lockedSubAccounts, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
for (Map.Entry<SubAccount, String> entry : lockedSubAccounts.entrySet()) {
try {
SubAccount subAccount = entry.getKey();
String subAccountString = subAccount.getChartOfAccountsCode() + " - " + subAccount.getAccountNumber() + " - " + subAccount.getSubAccountNumber();
if (StringUtils.isNotBlank(subAccountString)) {
String noteTextTemplate = kualiConfigurationService.getPropertyValueAsString(messageKey);
String noteText = MessageFormat.format(noteTextTemplate, subAccountString, entry.getValue());
Note note = noteService.createNote(noteTemplate, noteParent, GlobalVariables.getUserSession().getPrincipalId());
note.setNoteText(noteText);
noteService.save(note);
}
}
catch (Exception e) {
LOG.error("Unable to create/save notes for document " + documentNumber, e);
throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e);
}
}
}
protected void addNotes(String documentNumber, List<SubAccount> listOfSubAccounts, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
for (int i = 0; i < listOfSubAccounts.size(); i += getNumSubAccountsPerNote()) {
try {
String subAccountString = createSubAccountChunk(listOfSubAccounts, i, i + getNumSubAccountsPerNote());
if (StringUtils.isNotBlank(subAccountString)) {
String noteTextTemplate = kualiConfigurationService.getPropertyValueAsString(messageKey);
String noteText = MessageFormat.format(noteTextTemplate, subAccountString);
Note note = noteService.createNote(noteTemplate, noteParent, GlobalVariables.getUserSession().getPrincipalId());
note.setNoteText(noteText);
note.setNotePostedTimestampToCurrent();
noteService.save(note);
}
}
catch (Exception e) {
LOG.error("Unable to create/save notes for document " + documentNumber, e);
throw new RuntimeException("Unable to create/save notes for document " + documentNumber, e);
}
}
}
protected String createSubAccountChunk(List<SubAccount> listOfSubAccounts, int startIndex, int endIndex) {
StringBuilder buf = new StringBuilder();
for (int i = startIndex; i < endIndex && i < listOfSubAccounts.size(); i++) {
SubAccount subAccount = listOfSubAccounts.get(i);
buf.append(subAccount.getChartOfAccountsCode()).append(" - ").append(subAccount.getAccountNumber()).append(" - ")
.append(subAccount.getSubAccountNumber());
if (i + 1 < endIndex && i + 1 < listOfSubAccounts.size()) {
buf.append(", ");
}
}
return buf.toString();
}
protected int getNumSubAccountsPerNote() {
return 20;
}
protected MaintenanceLock verifyAllLocksFromThisDocument(List<MaintenanceLock> maintenanceLocks, String documentNumber) {
for (MaintenanceLock maintenanceLock : maintenanceLocks) {
String lockingDocNumber = maintenanceDocumentDao.getLockingDocumentNumber(maintenanceLock.getLockingRepresentation(), documentNumber);
if (StringUtils.isNotBlank(lockingDocNumber)) {
return maintenanceLock;
}
}
return null;
}
public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
}
public void setMaintenanceDocumentDao(MaintenanceDocumentDao maintenanceDocumentDao) {
this.maintenanceDocumentDao = maintenanceDocumentDao;
}
public void setNoteService(NoteService noteService) {
this.noteService = noteService;
}
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) {
this.documentHeaderService = documentHeaderService;
}
}