/*
* 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.pdp.batch;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.gl.batch.CollectorBatch;
import org.kuali.kfs.pdp.PdpConstants;
import org.kuali.kfs.pdp.PdpKeyConstants;
import org.kuali.kfs.pdp.batch.service.ExtractPaymentService;
import org.kuali.kfs.pdp.businessobject.LoadPaymentStatus;
import org.kuali.kfs.pdp.businessobject.PaymentAccountDetail;
import org.kuali.kfs.pdp.businessobject.PaymentDetail;
import org.kuali.kfs.pdp.businessobject.PaymentFileLoad;
import org.kuali.kfs.pdp.businessobject.PaymentGroup;
import org.kuali.kfs.pdp.businessobject.PaymentHeader;
import org.kuali.kfs.pdp.businessobject.PaymentNoteText;
import org.kuali.kfs.pdp.businessobject.ResearchParticipantPaymentDetail;
import org.kuali.kfs.pdp.businessobject.ResearchParticipantUpload;
import org.kuali.kfs.pdp.service.CustomerProfileService;
import org.kuali.kfs.pdp.service.PaymentFileService;
import org.kuali.kfs.pdp.service.ResearchParticipantPaymentValidationService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.batch.BatchInputFileTypeBase;
import org.kuali.kfs.sys.businessobject.MappingCSVReader;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiInteger;
import org.kuali.rice.kns.service.DictionaryValidationService;
import org.kuali.rice.krad.service.SequenceAccessorService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.location.api.country.Country;
import org.kuali.rice.location.api.country.CountryService;
import au.com.bytecode.opencsv.bean.ColumnPositionMappingStrategy;
// Created for Research Participant Upload
public class ResearchParticipantInboundServiceInputType extends BatchInputFileTypeBase {
private static Logger LOG = Logger.getLogger(ResearchParticipantInboundServiceInputType.class);
private static final String FILE_NAME_PREFIX = "research_participant_";
private static final String PAYMENT_HEADER = "Payment Header";
private static final String ACCOUNTING_LINE = "Accounting Line";
private static final String PAYMENT_DETAIL = "Payment Detail";
private static final String GL_DESCRIPTION = "GL Description";
private DateTimeService dateTimeService;
private CustomerProfileService customerProfileService;
private PaymentFileService paymentFileService;
private DictionaryValidationService dictionaryValidationService;
private SequenceAccessorService sequenceAccessorService;
private CountryService countryService;
private ResearchParticipantPaymentValidationService researchParticipantPaymentValidationService;
private ExtractPaymentService extractPaymentService;
/**
* @see org.kuali.kfs.sys.batch.BatchInputFileType#getFileTypeIdentifer()
*/
@Override
public String getFileTypeIdentifer() {
return PdpConstants.RESEARCH_PARTICIPANT_INPUT_FILE_TYPE_INDENTIFIER;
}
/**
* Builds the file name using the following construction: All research participant inbound service files start with
* research_participant_ append the username of the user who is uploading the file then the fileUserIdentifier
* then the timestamp.
*
* @param user who uploaded the file
* @param parsedFileContents represents collector batch object
* @param userIdentifier user identifier for user who uploaded file
* @return String returns file name using the convention mentioned in the description
*
* @see org.kuali.kfs.sys.batch.BatchInputFileType#getFileName(org.kuali.rice.kim.bo.Person, java.lang.Object,
* java.lang.String)
*/
@Override
public String getFileName(String principalName, Object parsedFileContents, String fileUserIdentifer) {
return "";
}
public DateTimeService getDateTimeService() {
return dateTimeService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
@Override
public void process(String fileName, Object parsedFileContents) {
LoadPaymentStatus status = new LoadPaymentStatus();
status.setMessageMap(new MessageMap());
paymentFileService.loadPayments((PaymentFileLoad)parsedFileContents, status, getFileName(GlobalVariables.getUserSession().getPrincipalName(), null, null));
}
public CustomerProfileService getCustomerProfileService() {
return customerProfileService;
}
public void setCustomerProfileService(CustomerProfileService customerProfileService) {
this.customerProfileService = customerProfileService;
}
public PaymentFileService getPaymentFileService() {
return paymentFileService;
}
public void setPaymentFileService(PaymentFileService paymentFileService) {
this.paymentFileService = paymentFileService;
}
public DictionaryValidationService getDictionaryValidationService() {
return dictionaryValidationService;
}
public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
this.dictionaryValidationService = dictionaryValidationService;
}
public SequenceAccessorService getSequenceAccessorService() {
return sequenceAccessorService;
}
public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) {
this.sequenceAccessorService = sequenceAccessorService;
}
public CountryService getCountryService() {
return countryService;
}
public void setCountryService(CountryService countryService) {
this.countryService = countryService;
}
public ExtractPaymentService getExtractPaymentService() {
return extractPaymentService;
}
public void setExtractPaymentService(ExtractPaymentService extractPaymentService) {
this.extractPaymentService = extractPaymentService;
}
/**
* Override the superclass method to parse the incoming spreadsheet
* file and convert into PaymentFileLoad, then return it to the invoker.
*
* @see org.kuali.kfs.sys.batch.BatchInputFileType#parse(byte[])
*/
@Override
public Object parse(byte[] fileByteContent) throws ParseException {
List<CollectorBatch> batchList = new ArrayList<CollectorBatch>();
ResearchParticipantUpload uploadFile = new ResearchParticipantUpload();
try {
InputStreamReader inputStreamReader = new InputStreamReader(new ByteArrayInputStream(fileByteContent));
MappingCSVReader reader = new MappingCSVReader(inputStreamReader);
String[] fileLine = null;
while ((fileLine = reader.readNext()) != null) {
ColumnPositionMappingStrategy strat = new ColumnPositionMappingStrategy();
if (fileLine[0].equalsIgnoreCase(PAYMENT_HEADER)) {
//If there's already a payment header on the uploadFile,
//we'll give error message to the user that something's wrong
//with the file format because the file can only have 1 payment
//header.
if (uploadFile.getPaymentHeader() != null) {
GlobalVariables.getMessageMap().putError("uploadFile", PdpKeyConstants.ERROR_BATCH_UPLOAD_BAD_FORMAT, PdpConstants.MULTIPLE_PAYMENT_HEADERS);
return null;
}
PaymentHeader ph = parsePaymentHeader(fileLine, reader, strat);
uploadFile.setPaymentHeader(ph);
}
else if (fileLine[0].equalsIgnoreCase(ACCOUNTING_LINE)) {
//If there's already an accounting line on the uploadFile,
//we'll give error message to the user that something's wrong
//with the file format because the file can only have 1
//accountingLine
if (uploadFile.getPaymentAccountDetail() != null) {
GlobalVariables.getMessageMap().putError("uploadFile", PdpKeyConstants.ERROR_BATCH_UPLOAD_BAD_FORMAT, PdpConstants.MULTIPLE_ACCOUNTS);
return null;
}
PaymentAccountDetail ac = parseAccountingLine(fileLine, reader, strat);
uploadFile.setPaymentAccountDetail(ac);
}
else if (fileLine[0].equalsIgnoreCase(PAYMENT_DETAIL)) {
ResearchParticipantPaymentDetail pd = parsePaymentDetail(fileLine, reader, strat);
uploadFile.addPaymentDetail(pd);
}
else if (fileLine[0].equalsIgnoreCase(GL_DESCRIPTION)) {
uploadFile.setGenericDescription(fileLine[1]);
}
}
}
catch (IOException ioe) {
LOG.error("Error encountered reading from file content", ioe);
throw new ParseException("Error encountered reading from file content", ioe);
}
if ((uploadFile.getPaymentDetails().size() < 1) || uploadFile.getPaymentHeader() == null || uploadFile.getPaymentAccountDetail() == null) {
GlobalVariables.getMessageMap().putError("uploadFile", PdpKeyConstants.ERROR_BATCH_UPLOAD_BAD_FORMAT, KFSConstants.EMPTY_STRING);
return null;
}
PaymentFileLoad paymentFileLoad = convertToPaymentFileLoad(uploadFile);
return paymentFileLoad;
}
/**
*
* Converts the ResearchParticipantUpload which represents the spreadsheet
* into the existing PaymentFileLoad.
* @param uploadFile
* @return
*/
protected PaymentFileLoad convertToPaymentFileLoad(ResearchParticipantUpload uploadFile) {
PaymentFileLoad paymentFileLoad = new PaymentFileLoad();
paymentFileLoad.setChart(uploadFile.getPaymentHeader().getChartOfAccountsCode());
paymentFileLoad.setUnit(uploadFile.getPaymentHeader().getUnit());
paymentFileLoad.setSubUnit(uploadFile.getPaymentHeader().getSubUnit());
paymentFileLoad.setCreationDate(uploadFile.getPaymentHeader().getCreationDate());
paymentFileLoad.setPaymentCount(uploadFile.getPaymentDetails().size());
paymentFileLoad.setPaymentTotalAmount(uploadFile.getPaymentTotalAmount());
paymentFileLoad.setPaymentGroups(createPaymentGroups(uploadFile));
KualiInteger batchId = new KualiInteger(sequenceAccessorService.getNextAvailableSequenceNumber(PdpConstants.SequenceNames.PDP_PMT_FIL_ID, PaymentFileLoad.class).intValue());
paymentFileLoad.setBatchId(batchId);
return paymentFileLoad;
}
/**
* Creates the list of payment groups given the upload file.
*
* @param uploadFile
* @return
*/
protected List<PaymentGroup> createPaymentGroups(ResearchParticipantUpload uploadFile) {
List<PaymentGroup> result = new ArrayList<PaymentGroup>();
for (ResearchParticipantPaymentDetail detail : uploadFile.getPaymentDetails()) {
String payeeName = detail.getPayeeName();
PaymentGroup paymentGroup = new PaymentGroup();
paymentGroup.setPayeeName(payeeName);
paymentGroup.setLine1Address(detail.getAddressLine1());
paymentGroup.setLine2Address(detail.getAddressLine2());
paymentGroup.setLine3Address(detail.getAddressLine3());
paymentGroup.setCity(detail.getCity());
paymentGroup.setState(detail.getState());
paymentGroup.setZipCd(detail.getZip());
paymentGroup.setPayeeIdTypeCd(uploadFile.getPaymentHeader().getVendorOrEmployee());
Country defaultCountry = countryService.getDefaultCountry();
paymentGroup.setCountry(defaultCountry.getCode());
paymentGroup.setPaymentDate(uploadFile.getPaymentHeader().getPaymentDate());
paymentGroup.setPaymentStatusCode(KFSConstants.PdpConstants.PAYMENT_OPEN_STATUS_CODE);
PaymentDetail paymentDetail = new PaymentDetail();
paymentDetail.setInvoiceDate(uploadFile.getPaymentHeader().getPaymentDate());
paymentDetail.setCustPaymentDocNbr(uploadFile.getPaymentHeader().getSourceDocNumber());
paymentDetail.setFinancialSystemOriginCode(KFSConstants.ORIGIN_CODE_KUALI);
List<String> formattedCheckNoteLines = this.getExtractPaymentService().formatCheckNoteLines(detail.getCheckStubText());
int count = 1;
for (String noteText : formattedCheckNoteLines) {
PaymentNoteText note = new PaymentNoteText();
note.setCustomerNoteText(noteText);
note.setCustomerNoteLineNbr(new KualiInteger(count++));
paymentDetail.addNote(note);
}
uploadFile.getPaymentAccountDetail().setAccountNetAmount(detail.getAmount());
PaymentAccountDetail ac = new PaymentAccountDetail();
ac.setFinChartCode(uploadFile.getPaymentAccountDetail().getFinChartCode());
ac.setAccountNbr(uploadFile.getPaymentAccountDetail().getAccountNbr());
ac.setFinObjectCode(uploadFile.getPaymentAccountDetail().getFinObjectCode());
ac.setOrgReferenceId(uploadFile.getPaymentAccountDetail().getOrgReferenceId());
ac.setAccountNetAmount(uploadFile.getPaymentAccountDetail().getAccountNetAmount());
// change nulls into ---'s for the fields that need it
if (StringUtils.isBlank(uploadFile.getPaymentAccountDetail().getFinSubObjectCode())) {
ac.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
}
else {
ac.setFinSubObjectCode(uploadFile.getPaymentAccountDetail().getFinSubObjectCode());
}
if (StringUtils.isBlank(uploadFile.getPaymentAccountDetail().getSubAccountNbr())) {
ac.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
}
else {
ac.setSubAccountNbr(uploadFile.getPaymentAccountDetail().getSubAccountNbr());
}
if (StringUtils.isBlank(uploadFile.getPaymentAccountDetail().getProjectCode())) {
ac.setProjectCode(KFSConstants.getDashProjectCode());
}
else {
ac.setProjectCode(uploadFile.getPaymentAccountDetail().getProjectCode());
}
paymentDetail.addAccountDetail(ac);
paymentGroup.addPaymentDetails(paymentDetail);
result.add(paymentGroup);
}
return result;
}
/**
* Parses the payment header information in the file.
*
* @param fileLine
* @param reader
* @param strat
* @return
*/
protected PaymentHeader parsePaymentHeader(String[] fileLine, MappingCSVReader reader, ColumnPositionMappingStrategy strat) {
strat.setType(PaymentHeader.class);
String[] headerColumns = new String[] { PdpConstants.PaymentHeader.CHART,
PdpConstants.PaymentHeader.UNIT, PdpConstants.PaymentHeader.SUBUNIT,
PdpConstants.PaymentHeader.CREATION_DATE, PdpConstants.PaymentHeader.VENDOR_OR_EMPLOYEE,
PdpConstants.PaymentHeader.SOURCE_DOC_NUMBER, PdpConstants.PaymentHeader.PAYMENT_DATE};
strat.setColumnMapping(headerColumns);
String[] newNextLine = Arrays.copyOfRange(fileLine, 1, fileLine.length);
PaymentHeader ph = (PaymentHeader) reader.processLine(strat, newNextLine);
return ph;
}
/**
* Parses the accounting line information in the file.
*
* @param fileLine
* @param reader
* @param strat
* @return
*/
protected PaymentAccountDetail parseAccountingLine(String[] fileLine, MappingCSVReader reader, ColumnPositionMappingStrategy strat) {
strat.setType(PaymentAccountDetail.class);
String[] headerColumns = new String[] { PdpConstants.PaymentAccountDetail.CHART,
PdpConstants.PaymentAccountDetail.ACCOUNT_NBR,
PdpConstants.PaymentAccountDetail.SUB_ACCOUNT_NBR, PdpConstants.PaymentAccountDetail.OBJECT_CODE,
PdpConstants.PaymentAccountDetail.SUB_OBJECT_CODE, PdpConstants.PaymentAccountDetail.PROJECT_CODE,
PdpConstants.PaymentAccountDetail.ORG_REF_ID };
strat.setColumnMapping(headerColumns);
String[] newNextLine = Arrays.copyOfRange(fileLine, 1, fileLine.length);
PaymentAccountDetail ac = (PaymentAccountDetail)reader.processLine(strat, newNextLine);
return ac;
}
/**
* Parses the payment detail information in the file.
*
* @param fileLine
* @param reader
* @param strat
* @return
*/
protected ResearchParticipantPaymentDetail parsePaymentDetail(String[] fileLine, MappingCSVReader reader, ColumnPositionMappingStrategy strat) {
strat.setType(ResearchParticipantPaymentDetail.class);
String[] headerColumns = new String[] { PdpConstants.PaymentDetail.PAYEE_NAME,
PdpConstants.PaymentDetail.ADDRESS_LINE_1, PdpConstants.PaymentDetail.ADDRESS_LINE_2,
PdpConstants.PaymentDetail.ADDRESS_LINE_3, PdpConstants.PaymentDetail.CITY,
PdpConstants.PaymentDetail.STATE, PdpConstants.PaymentDetail.ZIP,
PdpConstants.PaymentDetail.CHECK_STUB_TEXT, PdpConstants.PaymentDetail.AMOUNT };
strat.setColumnMapping(headerColumns);
String[] newNextLine = Arrays.copyOfRange(fileLine, 1, fileLine.length);
ResearchParticipantPaymentDetail pd = (ResearchParticipantPaymentDetail) reader.processLine(strat, newNextLine);
return pd;
}
@Override
public String getTitleKey() {
return PdpKeyConstants.MESSAGE_BATCH_UPLOAD_TITLE_RESEARCH_PARTICIPANT_FILE;
}
@Override
public String getAuthorPrincipalName(File file) {
return org.apache.commons.lang.StringUtils.substringBetween(file.getName(), FILE_NAME_PREFIX, PdpConstants.FILE_NAME_PART_DELIMITER);
}
/**
* Validates the information in the upload file using the doPaymentFileValidation
* in the existing paymentFileService from the foundation. In addition to that,
* it's also going to invoke Data Dictionary Validation for the PaymentFileLoad,
* for all the PaymentGroup objects and all of the PaymentDetail and PaymentAccountDetail
* objects to do some basic format validation such as length, requiredness, etc in place of
* the xml schema validation.
*
* @see org.kuali.kfs.sys.batch.BatchInputFileType#validate(java.lang.Object)
*/
@Override
public boolean validate(Object parsedFileContents) {
PaymentFileLoad paymentFile = (PaymentFileLoad)parsedFileContents;
dictionaryValidationService.validateBusinessObject(paymentFile);
for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
dictionaryValidationService.validateBusinessObject(paymentGroup);
for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
dictionaryValidationService.validateBusinessObject(paymentDetail);
for (PaymentAccountDetail account : paymentDetail.getAccountDetail()) {
dictionaryValidationService.validateBusinessObject(account);
}
}
}
MessageMap errorMessageMap = GlobalVariables.getMessageMap();
boolean isAccountDetailValid = this.getResearchParticipantPaymentValidationService().validatePaymentAccount(paymentFile, errorMessageMap);
if(isAccountDetailValid){
paymentFileService.doPaymentFileValidation(paymentFile, errorMessageMap);
}
return paymentFile.isPassedValidation();
}
/**
* Gets the researchParticipantPaymentValidationService attribute.
* @return Returns the researchParticipantPaymentValidationService.
*/
public ResearchParticipantPaymentValidationService getResearchParticipantPaymentValidationService() {
return researchParticipantPaymentValidationService;
}
/**
* Sets the researchParticipantPaymentValidationService attribute value.
* @param researchParticipantPaymentValidationService The researchParticipantPaymentValidationService to set.
*/
public void setResearchParticipantPaymentValidationService(ResearchParticipantPaymentValidationService researchParticipantPaymentValidationService) {
this.researchParticipantPaymentValidationService = researchParticipantPaymentValidationService;
}
/**
* For Research Participant Upload, we intentionally don't want to save
* the input file for security purposes. Therefore this method will return false.
*
* @see org.kuali.kfs.sys.batch.BatchInputFileTypeBase#shouldSave()
*/
@Override
public boolean shouldSave() {
return false;
}
}