/*
* 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.module.bc.document.service.impl;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.kuali.kfs.module.bc.BCConstants;
import org.kuali.kfs.module.bc.BCKeyConstants;
import org.kuali.kfs.module.bc.BCPropertyConstants;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockStatus;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPayRateHolding;
import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
import org.kuali.kfs.module.bc.document.dataaccess.PayrateImportDao;
import org.kuali.kfs.module.bc.document.service.BudgetDocumentService;
import org.kuali.kfs.module.bc.document.service.LockService;
import org.kuali.kfs.module.bc.document.service.PayrateImportService;
import org.kuali.kfs.module.bc.document.service.SalarySettingService;
import org.kuali.kfs.module.bc.exception.BudgetConstructionLockUnavailableException;
import org.kuali.kfs.module.bc.util.BudgetParameterFinder;
import org.kuali.kfs.module.bc.util.ExternalizedMessageWrapper;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.ObjectUtil;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.kfs.sys.service.OptionsService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.util.type.KualiInteger;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
public class PayrateImportServiceImpl implements PayrateImportService {
protected BusinessObjectService businessObjectService;
protected LockService lockService;
protected int importCount;
protected int updateCount;
protected OptionsService optionsService;
protected PayrateImportDao payrateImportDao;
protected BudgetDocumentService budgetDocumentService;
protected SalarySettingService salarySettingService;
/**
*
* @see org.kuali.kfs.module.bc.service.PayrateImportService#importFile(java.io.InputStream)
*/
@Transactional
public boolean importFile(InputStream fileImportStream, List<ExternalizedMessageWrapper> messageList, String principalId) {
Map payRateHoldingPersonUniversalIdentifierKey = new HashMap();
payRateHoldingPersonUniversalIdentifierKey.put(KFSPropertyConstants.PERSON_UNIVERSAL_IDENTIFIER, principalId);
this.businessObjectService.deleteMatching(BudgetConstructionPayRateHolding.class, payRateHoldingPersonUniversalIdentifierKey);
BufferedReader fileReader = new BufferedReader(new InputStreamReader(fileImportStream));
this.importCount = 0;
try {
while(fileReader.ready()) {
BudgetConstructionPayRateHolding budgetConstructionPayRateHolding = new BudgetConstructionPayRateHolding();
String line = fileReader.readLine();
ObjectUtil.convertLineToBusinessObject(budgetConstructionPayRateHolding, line, DefaultImportFileFormat.fieldLengths, Arrays.asList(DefaultImportFileFormat.fieldNames));
budgetConstructionPayRateHolding.setPrincipalId(principalId);
budgetConstructionPayRateHolding.setAppointmentRequestedPayRate(budgetConstructionPayRateHolding.getAppointmentRequestedPayRate().movePointLeft(2));
businessObjectService.save(budgetConstructionPayRateHolding);
this.importCount++;
}
}
catch (Exception e) {
this.businessObjectService.deleteMatching(BudgetConstructionPayRateHolding.class, payRateHoldingPersonUniversalIdentifierKey);
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_IMPORT_ABORTED));
return false;
}
if (importCount == 0 ) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.MSG_PAYRATE_IMPORT_NO_IMPORT_RECORDS));
}
else {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.MSG_PAYRATE_IMPORT_COUNT, String.valueOf(importCount)));
}
return true;
}
/**
*
* @see org.kuali.kfs.module.bc.service.PayrateImportService#update()
*/
@Transactional
public void update(Integer budgetYear, Person user, List<ExternalizedMessageWrapper> messageList, String principalId) {
List<PendingBudgetConstructionAppointmentFunding> lockedFundingRecords = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
boolean updateContainsErrors = false;
this.updateCount = 0;
Map payRateHoldingPersonUniversalIdentifierKey = new HashMap();
payRateHoldingPersonUniversalIdentifierKey.put(KFSPropertyConstants.PERSON_UNIVERSAL_IDENTIFIER, principalId);
List<BudgetConstructionPayRateHolding> records = (List<BudgetConstructionPayRateHolding>) this.businessObjectService.findMatching(BudgetConstructionPayRateHolding.class, payRateHoldingPersonUniversalIdentifierKey);
if ( !getPayrateLock(lockedFundingRecords, messageList, budgetYear, user, records) ) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_UPDATE_ABORTED, String.valueOf(this.updateCount)));
doRollback();
return;
}
Collection<String> biweeklyPayObjectCodes = BudgetParameterFinder.getBiweeklyPayObjectCodes();
for (BudgetConstructionPayRateHolding holdingRecord : records) {
if (holdingRecord.getAppointmentRequestedPayRate().equals( -1.0)) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_IMPORT_NO_PAYROLL_MATCH, holdingRecord.getEmplid(), holdingRecord.getPositionNumber()));
updateContainsErrors = true;
continue;
}
List<PendingBudgetConstructionAppointmentFunding> fundingRecords = this.payrateImportDao.getFundingRecords(holdingRecord, budgetYear, biweeklyPayObjectCodes);
if (fundingRecords.isEmpty()) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_NO_ACTIVE_FUNDING_RECORDS, holdingRecord.getEmplid(), holdingRecord.getPositionNumber()));
updateContainsErrors = true;
continue;
}
for (PendingBudgetConstructionAppointmentFunding fundingRecord : fundingRecords) {
if ( !fundingRecord.getAppointmentRequestedPayRate().equals(holdingRecord.getAppointmentRequestedPayRate()) ) {
if (fundingRecord.getAppointmentRequestedFteQuantity().equals(0) || fundingRecord.getAppointmentRequestedFteQuantity() == null) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_NO_UPDATE_FTE_ZERO_OR_BLANK, holdingRecord.getEmplid(), holdingRecord.getPositionNumber(), fundingRecord.getChartOfAccountsCode(), fundingRecord.getAccountNumber(), fundingRecord.getSubAccountNumber()));
updateContainsErrors = true;
continue;
}
BigDecimal fteQty = salarySettingService.calculateFteQuantity(fundingRecord.getBudgetConstructionPosition().getIuPayMonths(), fundingRecord.getAppointmentFundingMonth(), fundingRecord.getAppointmentRequestedTimePercent());
BigDecimal annualWorkingHours = BigDecimal.valueOf(BudgetParameterFinder.getAnnualWorkingHours());
BigDecimal totalPayHoursForYear = fteQty.multiply(annualWorkingHours);
KualiInteger annualAmount = new KualiInteger(holdingRecord.getAppointmentRequestedPayRate().multiply(totalPayHoursForYear));
KualiInteger updateAmount = annualAmount.subtract(fundingRecord.getAppointmentRequestedAmount());
BudgetConstructionHeader header = budgetDocumentService.getBudgetConstructionHeader(fundingRecord);
if (header == null ) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_NO_BUDGET_DOCUMENT, budgetYear.toString(), fundingRecord.getChartOfAccountsCode(), fundingRecord.getAccountNumber(), fundingRecord.getSubAccountNumber()));
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_OBJECT_LEVEL_ERROR, fundingRecord.getEmplid(), fundingRecord.getPositionNumber(), fundingRecord.getChartOfAccountsCode(), fundingRecord.getAccountNumber(), fundingRecord.getSubAccountNumber()));
updateContainsErrors = true;
continue;
}
// update or create pending budget GL record and plug line
budgetDocumentService.updatePendingBudgetGeneralLedger(fundingRecord, updateAmount);
if (updateAmount.isNonZero()) {
budgetDocumentService.updatePendingBudgetGeneralLedgerPlug(fundingRecord, updateAmount.negated());
}
fundingRecord.setAppointmentRequestedPayRate(holdingRecord.getAppointmentRequestedPayRate());
fundingRecord.setAppointmentRequestedAmount(annualAmount);
this.businessObjectService.save(fundingRecord);
}
}
this.updateCount ++;
}
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.MSG_PAYRATE_IMPORT_UPDATE_COMPLETE, String.valueOf(updateCount)));
for (PendingBudgetConstructionAppointmentFunding recordToUnlock : lockedFundingRecords) {
this.lockService.unlockAccount(budgetDocumentService.getBudgetConstructionHeader(recordToUnlock));
}
}
/**
*
* @see org.kuali.kfs.module.bc.service.PayrateImportService#generatePdf(java.lang.StringBuilder, java.io.ByteArrayOutputStream)
*/
@NonTransactional
public void generatePdf(List<ExternalizedMessageWrapper> logMessages, ByteArrayOutputStream baos) throws DocumentException {
Document document = new Document();
PdfWriter.getInstance(document, baos);
document.open();
for (ExternalizedMessageWrapper messageWrapper : logMessages) {
String message;
if (messageWrapper.getParams().length == 0 ) {
message = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(messageWrapper.getMessageKey());
}
else {
String temp = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(messageWrapper.getMessageKey());
message = MessageFormat.format(temp, (Object[])messageWrapper.getParams());
}
document.add(new Paragraph(message));
}
document.close();
}
/**
* Sets the business object service
*
* @param businessObjectService
*/
@NonTransactional
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
/**
* sets lock service
*
* @param lockService
*/
@NonTransactional
public void setLockService(LockService lockService) {
this.lockService = lockService;
}
/**
* sets option service
*
* @param optionsService
*/
@NonTransactional
public void setOptionsService(OptionsService optionsService) {
this.optionsService = optionsService;
}
/**
* sets payrate import dao
*
* @param payrateImportDao
*/
@NonTransactional
public void setPayrateImportDao(PayrateImportDao payrateImportDao) {
this.payrateImportDao = payrateImportDao;
}
/**
* Sets the budgetDocumentService attribute value.
* @param budgetDocumentService The budgetDocumentService to set.
*/
@NonTransactional
public void setBudgetDocumentService(BudgetDocumentService budgetDocumentService) {
this.budgetDocumentService = budgetDocumentService;
}
/**
* Sets the salarySettingService attribute.
*
* @param salarySettingService The salarySettingService to set.
*/
@NonTransactional
public void setSalarySettingService(SalarySettingService salarySettingService) {
this.salarySettingService = salarySettingService;
}
/**
* Creates the locking key to use in retrieving account locks
*
* @param record
* @return
*/
protected String getLockingKeyString(PendingBudgetConstructionAppointmentFunding record) {
return record.getUniversityFiscalYear() + "-" + record.getChartOfAccountsCode() + "-" + record.getAccountNumber() + "-" + record.getSubAccountNumber();
}
/**
* Retrieves Account locks for payrate import records
*
* @param lockMap
* @param messageList
* @param budgetYear
* @param user
* @param records
* @return
*/
@Transactional
protected boolean getPayrateLock(List<PendingBudgetConstructionAppointmentFunding> lockedRecords, List<ExternalizedMessageWrapper> messageList, Integer budgetYear, Person user, List<BudgetConstructionPayRateHolding> records) {
Collection<String> biweeklyPayObjectCodes = BudgetParameterFinder.getBiweeklyPayObjectCodes();
for (BudgetConstructionPayRateHolding record: records) {
List<PendingBudgetConstructionAppointmentFunding> fundingRecords = this.payrateImportDao.getFundingRecords(record, budgetYear, biweeklyPayObjectCodes);
try {
lockedRecords.addAll(this.lockService.lockPendingBudgetConstructionAppointmentFundingRecords(fundingRecords, user));
} catch(BudgetConstructionLockUnavailableException e) {
BudgetConstructionLockStatus lockStatus = e.getLockStatus();
if ( lockStatus.getLockStatus().equals(BCConstants.LockStatus.BY_OTHER) ) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_ACCOUNT_LOCK_EXISTS));
return false;
} else if ( lockStatus.getLockStatus().equals(BCConstants.LockStatus.FLOCK_FOUND) ) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_FUNDING_LOCK_EXISTS));
return false;
} else if ( !lockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS) ) {
messageList.add(new ExternalizedMessageWrapper(BCKeyConstants.ERROR_PAYRATE_BATCH_ACCOUNT_LOCK_FAILED));
return false;
}
}
}
return true;
}
/**
* File format for payrate import file
*
*/
protected static class DefaultImportFileFormat {
private static final int[] fieldLengths = new int[] {11, 8, 50, 5, 4, 3, 3, 10, 8};
private static final String[] fieldNames = new String[] {KFSPropertyConstants.EMPLID, KFSPropertyConstants.POSITION_NUMBER, KFSPropertyConstants.PERSON_NAME, BCPropertyConstants.SET_SALARY_ID, BCPropertyConstants.SALARY_ADMINISTRATION_PLAN, BCPropertyConstants.GRADE, "unionCode", BCPropertyConstants.APPOINTMENT_REQUESTED_PAY_RATE, BCPropertyConstants.CSF_FREEZE_DATE};
}
/**
* If retrieving budget locks fails, this method rolls back previous changes
*
*/
protected void doRollback() {
PlatformTransactionManager transactionManager = SpringContext.getBean(PlatformTransactionManager.class);
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition);
transactionManager.rollback( transactionStatus );
}
}