/*
* 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.Collection;
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.ObjectCode;
import org.kuali.kfs.coa.businessobject.SubObjectCode;
import org.kuali.kfs.coa.service.SubObjectTrickleDownInactivationService;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.service.UniversityDateService;
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.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentHeaderService;
import org.kuali.rice.krad.service.NoteService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class SubObjectTrickleDownInactivationServiceImpl implements SubObjectTrickleDownInactivationService {
private static final int NO_OF_SUB_OBJECTS_PER_NOTE = 15;
private static final Logger LOG = Logger.getLogger(SubObjectTrickleDownInactivationServiceImpl.class);
protected BusinessObjectService businessObjectService;
protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
protected MaintenanceDocumentDao maintenanceDocumentDao;
protected NoteService noteService;
protected ConfigurationService kualiConfigurationService;
protected UniversityDateService universityDateService;
protected DocumentHeaderService documentHeaderService;
public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Account inactivatedAccount, String documentNumber) {
Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedAccount);
List<MaintenanceLock> maintenanceLocks = generateTrickleDownMaintenanceLocks(subObjects, documentNumber);
return maintenanceLocks;
}
public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(ObjectCode inactivatedObjectCode, String documentNumber) {
Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedObjectCode);
List<MaintenanceLock> maintenanceLocks = generateTrickleDownMaintenanceLocks(subObjects, documentNumber);
return maintenanceLocks;
}
public List<MaintenanceLock> generateTrickleDownMaintenanceLocks(Collection<SubObjectCode> subObjects, String documentNumber) {
Maintainable subObjectMaintainable = getSubObjectMaintainable(documentNumber);
List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
for (SubObjectCode subObjCd : subObjects) {
subObjectMaintainable.setBusinessObject(subObjCd);
maintenanceLocks.addAll(subObjectMaintainable.generateMaintenanceLocks());
}
return maintenanceLocks;
}
protected class TrickleDownInactivationStatus {
public List<SubObjectCode> inactivatedSubObjCds;
public Map<SubObjectCode, String> alreadyLockedSubObjCds;
public List<SubObjectCode> errorPersistingSubObjCds;
public TrickleDownInactivationStatus() {
inactivatedSubObjCds = new ArrayList<SubObjectCode>();
alreadyLockedSubObjCds = new HashMap<SubObjectCode, String>();
errorPersistingSubObjCds = new ArrayList<SubObjectCode>();
}
}
public void trickleDownInactivateSubObjects(Account inactivatedAccount, String documentNumber) {
Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedAccount);
TrickleDownInactivationStatus trickleDownInactivationStatus = trickleDownInactivate(subObjects, documentNumber);
addNotesToDocument(trickleDownInactivationStatus, documentNumber);
}
public void trickleDownInactivateSubObjects(ObjectCode inactivatedObject, String documentNumber) {
Collection<SubObjectCode> subObjects = getAssociatedSubObjects(inactivatedObject);
TrickleDownInactivationStatus trickleDownInactivationStatus = trickleDownInactivate(subObjects, documentNumber);
addNotesToDocument(trickleDownInactivationStatus, documentNumber);
}
protected TrickleDownInactivationStatus trickleDownInactivate(Collection<SubObjectCode> subObjects, String documentNumber) {
TrickleDownInactivationStatus trickleDownInactivationStatus = new TrickleDownInactivationStatus();
if (subObjects != null && !subObjects.isEmpty()) {
Maintainable subObjectMaintainable = getSubObjectMaintainable(documentNumber);
for (Iterator<SubObjectCode> i = subObjects.iterator(); i.hasNext(); ) {
SubObjectCode subObjCd = i.next();
if (subObjCd.isActive()) {
subObjectMaintainable.setBusinessObject(subObjCd);
List<MaintenanceLock> subAccountLocks = subObjectMaintainable.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
trickleDownInactivationStatus.alreadyLockedSubObjCds.put(subObjCd, 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
subObjCd.setActive(false);
try {
subObjectMaintainable.saveBusinessObject();
trickleDownInactivationStatus.inactivatedSubObjCds.add(subObjCd);
}
catch (RuntimeException e) {
LOG.error("Unable to trickle-down inactivate sub-account " + subObjCd.toString(), e);
trickleDownInactivationStatus.errorPersistingSubObjCds.add(subObjCd);
}
}
}
}
}
return trickleDownInactivationStatus;
}
protected void addNotesToDocument(TrickleDownInactivationStatus trickleDownInactivationStatus, String documentNumber) {
if (trickleDownInactivationStatus.inactivatedSubObjCds.isEmpty() && trickleDownInactivationStatus.alreadyLockedSubObjCds.isEmpty() && trickleDownInactivationStatus.errorPersistingSubObjCds.isEmpty()) {
// if we didn't try to inactivate any sub-objects, then don't bother
return;
}
DocumentHeader noteParent = documentHeaderService.getDocumentHeaderById(documentNumber);
Note newNote = new Note();
addNotes(documentNumber, trickleDownInactivationStatus.inactivatedSubObjCds, KFSKeyConstants.SUB_OBJECT_TRICKLE_DOWN_INACTIVATION, noteParent, newNote);
addNotes(documentNumber, trickleDownInactivationStatus.errorPersistingSubObjCds, KFSKeyConstants.SUB_OBJECT_TRICKLE_DOWN_INACTIVATION_ERROR_DURING_PERSISTENCE, noteParent, newNote);
addMaintenanceLockedNotes(documentNumber, trickleDownInactivationStatus.alreadyLockedSubObjCds, KFSKeyConstants.SUB_OBJECT_TRICKLE_DOWN_INACTIVATION_RECORD_ALREADY_MAINTENANCE_LOCKED, noteParent, newNote);
}
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;
}
protected Maintainable getSubObjectMaintainable(String documentNumber) {
Maintainable subObjectMaintainable;
try {
subObjectMaintainable = (Maintainable) maintenanceDocumentDictionaryService.getMaintainableClass(SubObjectCode.class.getName()).newInstance();
subObjectMaintainable.setBoClass(SubObjectCode.class);
subObjectMaintainable.setDocumentNumber(documentNumber);
}
catch (Exception e) {
LOG.error("Unable to instantiate SubObject Maintainable" , e);
throw new RuntimeException("Unable to instantiate SubObject Maintainable" , e);
}
return subObjectMaintainable;
}
protected Collection<SubObjectCode> getAssociatedSubObjects(Account account) {
Map<String, Object> fieldValues = new HashMap<String, Object>();
fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, universityDateService.getCurrentFiscalYear());
fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, account.getChartOfAccountsCode());
fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, account.getAccountNumber());
return businessObjectService.findMatching(SubObjectCode.class, fieldValues);
}
protected Collection<SubObjectCode> getAssociatedSubObjects(ObjectCode objectCode) {
Map<String, Object> fieldValues = new HashMap<String, Object>();
fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, objectCode.getUniversityFiscalYear());
fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, objectCode.getChartOfAccountsCode());
fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, objectCode.getFinancialObjectCode());
return businessObjectService.findMatching(SubObjectCode.class, fieldValues);
}
protected void addNotes(String documentNumber, List<SubObjectCode> listOfSubObjects, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
for (int i = 0; i < listOfSubObjects.size(); i += getNumSubObjectsPerNote()) {
try {
String subAccountString = createSubObjectChunk(listOfSubObjects, i, i + getNumSubObjectsPerNote());
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 void addMaintenanceLockedNotes(String documentNumber, Map<SubObjectCode, String> lockedSubObjects, String messageKey, PersistableBusinessObject noteParent, Note noteTemplate) {
for (Map.Entry<SubObjectCode, String> entry : lockedSubObjects.entrySet()) {
try {
SubObjectCode subObjCd = entry.getKey();
String subObjectString = subObjCd.getUniversityFiscalYear() + " - " + subObjCd.getChartOfAccountsCode() + " - " + subObjCd.getAccountNumber() + " - " + subObjCd.getFinancialObjectCode() + " - " + subObjCd.getFinancialSubObjectCode();
if (StringUtils.isNotBlank(subObjectString)) {
String noteTextTemplate = kualiConfigurationService.getPropertyValueAsString(messageKey);
String noteText = MessageFormat.format(noteTextTemplate, subObjectString, entry.getValue());
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 createSubObjectChunk(List<SubObjectCode> listOfSubObjects, int startIndex, int endIndex) {
StringBuilder buf = new StringBuilder();
for (int i = startIndex; i < endIndex && i < listOfSubObjects.size(); i++) {
SubObjectCode subObjCd = listOfSubObjects.get(i);
buf.append(subObjCd.getUniversityFiscalYear()).append(" - ").append(subObjCd.getChartOfAccountsCode()).append(" - ")
.append(subObjCd.getAccountNumber()).append(" - ").append(subObjCd.getFinancialObjectCode())
.append(" - ").append(subObjCd.getFinancialSubObjectCode());
if (i + 1 < endIndex && i + 1 < listOfSubObjects.size()) {
buf.append(", ");
}
}
return buf.toString();
}
protected int getNumSubObjectsPerNote() {
//Account Document in Exception reduced the no of sub objects
//per note from 20 to 15 to reduce the note text length
return NO_OF_SUB_OBJECTS_PER_NOTE;
}
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
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 setUniversityDateService(UniversityDateService universityDateService) {
this.universityDateService = universityDateService;
}
public void setDocumentHeaderService(DocumentHeaderService documentHeaderService) {
this.documentHeaderService = documentHeaderService;
}
}