/*
* 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.tem.batch.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemKeyConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.TemConstants.CreditCardStagingDataErrorCodes;
import org.kuali.kfs.module.tem.TemConstants.ExpenseImport;
import org.kuali.kfs.module.tem.TemConstants.ExpenseTypes;
import org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService;
import org.kuali.kfs.module.tem.batch.service.DataReportService;
import org.kuali.kfs.module.tem.businessobject.CreditCardAgency;
import org.kuali.kfs.module.tem.businessobject.CreditCardImportData;
import org.kuali.kfs.module.tem.businessobject.CreditCardStagingData;
import org.kuali.kfs.module.tem.businessobject.ExpenseType;
import org.kuali.kfs.module.tem.businessobject.HistoricalTravelExpense;
import org.kuali.kfs.module.tem.businessobject.TemProfileAccount;
import org.kuali.kfs.module.tem.service.CreditCardAgencyService;
import org.kuali.kfs.module.tem.service.TemProfileService;
import org.kuali.kfs.module.tem.service.TravelExpenseService;
import org.kuali.kfs.sys.batch.BatchInputFileType;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.report.BusinessObjectReportHelper;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.ErrorMessage;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
public class CreditCardDataImportServiceImpl implements CreditCardDataImportService{
public static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreditCardDataImportServiceImpl.class);
public final static String REPORT_FILE_NAME_PATTERN = "{0}/{1}_{2}{3}";
private BatchInputFileService batchInputFileService;
private BusinessObjectService businessObjectService;
private DateTimeService dateTimeService;
private TemProfileService temProfileService;
private TravelExpenseService travelExpenseService;
private CreditCardAgencyService creditCardAgencyService;
private DataReportService dataReportService;
private List<BatchInputFileType> creditCardDataImportFileTypes;
private String creditCardDataFileErrorDirectory;
private BusinessObjectReportHelper creditCardDataUploadReportHelper;
private String creditCardDataReportDirectory;
private String creditCardDataReportFilePrefix;
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#importCreditCardData()
*/
@Override
public boolean importCreditCardData() {
boolean success = true;
for (BatchInputFileType inputFileType : creditCardDataImportFileTypes) {
List<String> inputFileNames = batchInputFileService.listInputFileNamesWithDoneFile(inputFileType);
for (String dataFileName : inputFileNames) {
success &= importCreditCardDataFile(dataFileName, inputFileType);
}
}
return success;
}
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#importCreditCardDataFile(java.lang.String, org.kuali.kfs.sys.batch.BatchInputFileType)
*/
@Override
public boolean importCreditCardDataFile(String dataFileName, BatchInputFileType inputFileType) {
try {
FileInputStream fileContents = new FileInputStream(dataFileName);
byte[] fileByteContent = IOUtils.toByteArray(fileContents);
CreditCardImportData creditCardData = (CreditCardImportData) batchInputFileService.parse(inputFileType, fileByteContent);
IOUtils.closeQuietly(fileContents);
LOG.info("Credit Card Import - validating: " + dataFileName);
List<CreditCardStagingData> validCreditCardList = validateCreditCardData(creditCardData, dataFileName);
if (!validCreditCardList.isEmpty()){
businessObjectService.save(validCreditCardList);
}
}
catch (Exception ex) {
LOG.error("Failed to process the file : " + dataFileName, ex);
moveErrorFile(dataFileName, creditCardDataFileErrorDirectory);
return false;
}
finally {
removeDoneFiles(dataFileName);
}
return true;
}
public void moveErrorFile(String dataFileName, String creditCardDataFileErrorDirectory) {
File dataFile = new File(dataFileName);
if (!dataFile.exists() || !dataFile.canRead()) {
LOG.error("Cannot find/read data file " + dataFileName);
}
else {
try {
FileUtils.moveToDirectory(dataFile, new File(creditCardDataFileErrorDirectory), true);
}
catch (IOException ex) {
LOG.error("Cannot move the file:" + dataFile + " to the directory: " + creditCardDataFileErrorDirectory, ex);
}
}
}
/**
* Clears out associated .done files for the processed data files.
*/
protected void removeDoneFiles(String dataFileName) {
File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + TemConstants.DONE_FILE_SUFFIX);
if (doneFile.exists()) {
doneFile.delete();
}
}
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#validateCreditCardData(org.kuali.kfs.module.tem.businessobject.CreditCardImportData, java.lang.String)
*/
@Override
public List<CreditCardStagingData> validateCreditCardData(CreditCardImportData creditCardList, String dataFileName) {
PrintStream reportDataStream = dataReportService.getReportPrintStream(getCreditCardDataReportDirectory(), getCreditCardDataReportFilePrefix());
List<CreditCardStagingData> validData = new ArrayList<CreditCardStagingData>();
try {
dataReportService.writeReportHeader(reportDataStream, dataFileName, TemKeyConstants.MESSAGE_CREDIT_CARD_DATA_REPORT_HEADER, getCreditCardDataUploadReportHelper());
Integer count = 1;
for(CreditCardStagingData creditCardData: creditCardList.getCreditCardData()){
LOG.info("Validating credit card import. Record# " + count + " of " + creditCardList.getCreditCardData().size());
creditCardData.setErrorCode(CreditCardStagingDataErrorCodes.CREDIT_CARD_NO_ERROR);
creditCardData.setImportBy(creditCardList.getImportBy());
creditCardData.setStagingFileName(StringUtils.substringAfterLast(dataFileName, File.separator));
List<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>();
if(validateAndSetCreditCardAgency(creditCardData)){
if(creditCardData.getExpenseImport() == ExpenseImport.traveler){
TemProfileAccount temProfileAccount = findTraveler(creditCardData);
if(ObjectUtils.isNotNull(temProfileAccount)){
//Set Traveler Id
if(ObjectUtils.isNull(creditCardData.getTravelerId()) || creditCardData.getTravelerId() == 0){
Integer travelerId = new Integer(temProfileAccount.getProfile().getEmployeeId()).intValue();
creditCardData.setTravelerId(travelerId);
}
creditCardData.setTemProfileId(temProfileAccount.getProfileId());
//Set expense type code to O-Other if null
if(creditCardData.getExpenseTypeCode() == null){
creditCardData.setExpenseTypeCode(ExpenseTypes.OTHER);
}
// write an error if the expense type code is not valid
final ExpenseType expenseType = businessObjectService.findBySinglePrimaryKey(ExpenseType.class, creditCardData.getExpenseTypeCode());
if (ObjectUtils.isNotNull(expenseType)) {
//Set Credit Card Key(traveler Id + Credit Card Agency + Credit Card number
creditCardData.setCreditCardKey(creditCardData.getTravelerId() + temProfileAccount.getCreditCardAgency().getCreditCardOrAgencyCode()+ creditCardData.getCreditCardNumber());
// need to do the duplicate check at this point, since the CC key is one of the fields being checked
if (!isDuplicate(creditCardData, errorMessages)) {
creditCardData.setMoveToHistoryIndicator(true);
creditCardData.setProcessingTimestamp(dateTimeService.getCurrentTimestamp());
validData.add(creditCardData);
}
}
else {
LOG.error("Invalid expense type code "+creditCardData.getExpenseTypeCode()+" in Credit Card Data record");
errorMessages.add(new ErrorMessage(TemKeyConstants.MESSAGE_CREDIT_CARD_DATA_INVALID_EXPENSE_TYPE_CODE, creditCardData.getExpenseTypeCode()));
}
}
else {
LOG.error("No traveler found for credit card number: "+ creditCardData.getCreditCardNumber());
errorMessages.add(new ErrorMessage(TemKeyConstants.MESSAGE_CREDIT_CARD_DATA_NO_TRAVELER_FOUND, creditCardData.getCreditCardNumber()));
}
}
else if(creditCardData.getExpenseImport() == ExpenseImport.trip){
if (!isDuplicate(creditCardData, errorMessages)) {
creditCardData.setProcessingTimestamp(dateTimeService.getCurrentTimestamp());
validData.add(creditCardData);
}
}
}else{
errorMessages.add(new ErrorMessage(TemKeyConstants.MESSAGE_AGENCY_CREDIT_CARD_DATA_INVALID_CCA, creditCardData.getCreditCardOrAgencyCode()));
}
//writer to error report
if (!errorMessages.isEmpty()){
dataReportService.writeToReport(reportDataStream, creditCardData, errorMessages, getCreditCardDataUploadReportHelper());
}
count++;
}
}
finally {
if (reportDataStream != null) {
reportDataStream.flush();
reportDataStream.close();
}
}
return validData;
}
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#isDuplicate(org.kuali.kfs.module.tem.businessobject.CreditCardStagingData, java.util.List)
*/
@Override
public boolean isDuplicate(CreditCardStagingData creditCardData, List<ErrorMessage> errorMessages){
Map<String, Object> fieldValues = new HashMap<String, Object>();
if(StringUtils.isNotEmpty(creditCardData.getCreditCardKey())){
fieldValues.put(TemPropertyConstants.CREDIT_CARD_KEY, creditCardData.getCreditCardKey());
}
if(StringUtils.isNotEmpty(creditCardData.getReferenceNumber())){
fieldValues.put(TemPropertyConstants.REFERENCE_NUMBER, creditCardData.getReferenceNumber());
}
if(ObjectUtils.isNotNull(creditCardData.getTransactionAmount())){
fieldValues.put(TemPropertyConstants.TRANSACTION_AMOUNT, creditCardData.getTransactionAmount());
}
if(ObjectUtils.isNotNull(creditCardData.getTransactionDate())){
fieldValues.put(TemPropertyConstants.TRANSACTION_DATE, creditCardData.getTransactionDate());
}
if(ObjectUtils.isNotNull(creditCardData.getBankPostDate())){
fieldValues.put(TemPropertyConstants.BANK_POSTED_DATE, creditCardData.getBankPostDate());
}
if(StringUtils.isNotEmpty(creditCardData.getMerchantName())){
fieldValues.put(TemPropertyConstants.MERCHANT_NAME, creditCardData.getMerchantName());
}
List<CreditCardStagingData> creditCardDataList = (List<CreditCardStagingData>) businessObjectService.findMatching(CreditCardStagingData.class, fieldValues);
if (ObjectUtils.isNull(creditCardDataList) || creditCardDataList.size() == 0) {
return false;
}
LOG.error("Found a duplicate entry for credit card. Matching credit card id: " + creditCardDataList.get(0).getId());
SimpleDateFormat format = new SimpleDateFormat();
ErrorMessage error = new ErrorMessage(TemKeyConstants.MESSAGE_CREDIT_CARD_DATA_DUPLICATE_RECORD,
creditCardData.getCreditCardKey(), creditCardData.getReferenceNumber(), creditCardData.getTransactionAmount().toString(),
format.format(creditCardData.getTransactionDate()), format.format(creditCardData.getBankPostDate()), creditCardData.getMerchantName());
errorMessages.add(error);
return true;
}
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#findTraveler(org.kuali.kfs.module.tem.businessobject.CreditCardStagingData)
*/
@Override
public TemProfileAccount findTraveler(CreditCardStagingData creditCardData){
Map<String,String> criteria = new HashMap<String,String>(1);
criteria.put(TemPropertyConstants.ACCOUNT_NUMBER, creditCardData.getCreditCardNumber());
Collection<TemProfileAccount> temProfileAccounts = businessObjectService.findMatching(TemProfileAccount.class, criteria);
if(ObjectUtils.isNotNull(temProfileAccounts) && temProfileAccounts.size() > 0) {
return temProfileAccounts.iterator().next();
}
return null;
}
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#validateAndSetCreditCardAgency(org.kuali.kfs.module.tem.businessobject.CreditCardStagingData)
*/
@Override
public boolean validateAndSetCreditCardAgency(CreditCardStagingData creditCardData){
CreditCardAgency ccAgency = creditCardAgencyService.getCreditCardAgencyByCode(creditCardData.getCreditCardOrAgencyCode());
if (ObjectUtils.isNull(ccAgency)) {
LOG.error("Mandatory Field Credit Card Or Agency Code is invalid: " + creditCardData.getCreditCardOrAgencyCode());
creditCardData.setErrorCode(CreditCardStagingDataErrorCodes.CREDIT_CARD_INVALID_CC_AGENCY);
return false;
}
creditCardData.setCreditCardAgency(ccAgency);
creditCardData.setCreditCardOrAgencyCode(ccAgency.getCreditCardOrAgencyCode());
return true;
}
/**
* @see org.kuali.kfs.module.tem.batch.service.CreditCardDataImportService#moveCreditCardDataToHistoricalExpenseTable()
*/
@Override
public boolean moveCreditCardDataToHistoricalExpenseTable() {
List<CreditCardStagingData> creditCardData = travelExpenseService.retrieveValidCreditCardData();
if (ObjectUtils.isNotNull(creditCardData) && creditCardData.size() > 0) {
for(CreditCardStagingData creditCard: creditCardData){
boolean result = processCreditCardStagingExpense(creditCard);
LOG.info("Credit Card Staging Data Id: "+ creditCard.getId() + (result ? " was":" was not") + " processed.");
}
}
return true;
}
@Transactional
protected boolean processCreditCardStagingExpense(CreditCardStagingData creditCard) {
LOG.info("Creating historical travel expense for credit card: " + creditCard.getId());
HistoricalTravelExpense expense = travelExpenseService.createHistoricalTravelExpense(creditCard);
businessObjectService.save(expense);
//Mark as moved to historical
creditCard.setErrorCode(CreditCardStagingDataErrorCodes.CREDIT_CARD_MOVED_TO_HISTORICAL);
LOG.info("Finished creating historical travel expense for credit card: " + creditCard.getId() + " Historical Travel Expense: " + expense.getId());
businessObjectService.save(creditCard);
return true;
}
/**
* Sets the batchInputFileService attribute value.
* @param batchInputFileService The batchInputFileService to set.
*/
public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
this.batchInputFileService = batchInputFileService;
}
/**
* Sets the creditCardDataImportFileTypes attribute value.
* @param creditCardDataImportFileTypes The creditCardDataImportFileTypes to set.
*/
public void setCreditCardDataImportFileTypes(List<BatchInputFileType> creditCardDataImportFileTypes) {
this.creditCardDataImportFileTypes = creditCardDataImportFileTypes;
}
/**
* Sets the businessObjectService attribute value.
* @param businessObjectService The businessObjectService to set.
*/
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
/**
* Sets the creditCardDataFileErrorDirectory attribute value.
* @param creditCardDataFileErrorDirectory The creditCardDataFileErrorDirectory to set.
*/
public void setCreditCardDataFileErrorDirectory(String creditCardDataFileErrorDirectory) {
this.creditCardDataFileErrorDirectory = creditCardDataFileErrorDirectory;
}
/**
* Sets the temProfileService attribute value.
* @param temProfileService The temProfileService to set.
*/
public void setTemProfileService(TemProfileService temProfileService) {
this.temProfileService = temProfileService;
}
/**
*
* This method...
* @param argTravelExpenseService
*/
public void setTravelExpenseService(TravelExpenseService argTravelExpenseService){
this.travelExpenseService = argTravelExpenseService;
}
/**
* Sets the dateTimeService attribute value.
* @param dateTimeService The dateTimeService to set.
*/
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setCreditCardAgencyService(CreditCardAgencyService creditCardAgencyService) {
this.creditCardAgencyService = creditCardAgencyService;
}
public void setCreditCardDataUploadReportHelper(BusinessObjectReportHelper creditCardDataUploadReportHelper) {
this.creditCardDataUploadReportHelper = creditCardDataUploadReportHelper;
}
public void setCreditCardDataReportDirectory(String creditCardDataReportDirectory) {
this.creditCardDataReportDirectory = creditCardDataReportDirectory;
}
public void setCreditCardDataReportFilePrefix(String creditCardDataReportFilePrefix) {
this.creditCardDataReportFilePrefix = creditCardDataReportFilePrefix;
}
public BusinessObjectReportHelper getCreditCardDataUploadReportHelper() {
return creditCardDataUploadReportHelper;
}
public String getCreditCardDataReportDirectory() {
return creditCardDataReportDirectory;
}
public String getCreditCardDataReportFilePrefix() {
return creditCardDataReportFilePrefix;
}
public DataReportService getDataReportService() {
return dataReportService;
}
public void setDataReportService(DataReportService dataReportService) {
this.dataReportService = dataReportService;
}
}