/*
* 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.gl.batch.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.BalanceType;
import org.kuali.kfs.coa.businessobject.ObjectType;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.batch.CollectorBatch;
import org.kuali.kfs.gl.batch.CollectorStep;
import org.kuali.kfs.gl.batch.service.CollectorHelperService;
import org.kuali.kfs.gl.batch.service.CollectorScrubberService;
import org.kuali.kfs.gl.businessobject.CollectorDetail;
import org.kuali.kfs.gl.businessobject.CollectorHeader;
import org.kuali.kfs.gl.businessobject.OriginEntryFull;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.gl.report.CollectorReportData;
import org.kuali.kfs.gl.report.PreScrubberReportData;
import org.kuali.kfs.gl.service.CollectorDetailService;
import org.kuali.kfs.gl.service.OriginEntryGroupService;
import org.kuali.kfs.gl.service.OriginEntryService;
import org.kuali.kfs.gl.service.PreScrubberService;
import org.kuali.kfs.gl.service.impl.CollectorScrubberStatus;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.batch.BatchInputFileType;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* The base implementation of CollectorHelperService
* @see org.kuali.kfs.gl.batch.service.CollectorService
*/
public class CollectorHelperServiceImpl implements CollectorHelperService {
private static Logger LOG = Logger.getLogger(CollectorHelperServiceImpl.class);
private static final String CURRENCY_SYMBOL = "$";
private CollectorDetailService collectorDetailService;
private OriginEntryService originEntryService;
private OriginEntryGroupService originEntryGroupService;
private ParameterService parameterService;
private ConfigurationService configurationService;
private DateTimeService dateTimeService;
private BatchInputFileService batchInputFileService;
private CollectorScrubberService collectorScrubberService;
private AccountService accountService;
private PreScrubberService preScrubberService;
private String batchFileDirectoryName;
/**
* Parses the given file, validates the batch, stores the entries, and sends email.
* @param fileName - name of file to load (including path)
* @param group the group into which to persist the origin entries for the collector batch/file
* @param collectorReportData the object used to store all of the collector status information for reporting
* @param collectorScrubberStatuses if the collector scrubber is able to be invoked upon this collector batch, then the status
* info of the collector status run is added to the end of this list
* @param the output stream to which to store origin entries that properly pass validation
* @return boolean - true if load was successful, false if errors were encountered
* @see org.kuali.kfs.gl.batch.service.CollectorService#loadCollectorFile(java.lang.String)
*/
public boolean loadCollectorFile(String fileName, CollectorReportData collectorReportData, List<CollectorScrubberStatus> collectorScrubberStatuses, BatchInputFileType collectorInputFileType, PrintStream originEntryOutputPs) {
boolean isValid = true;
MessageMap fileMessageMap = collectorReportData.getMessageMapForFileName(fileName);
List<CollectorBatch> batches = doCollectorFileParse(fileName, fileMessageMap, collectorInputFileType, collectorReportData);
for (int i = 0; i < batches.size(); i++) {
CollectorBatch collectorBatch = batches.get(i);
collectorBatch.setBatchName(fileName + " Batch " + String.valueOf(i + 1));
collectorReportData.addBatch(collectorBatch);
isValid &= loadCollectorBatch(collectorBatch, fileName, i + 1, collectorReportData, collectorScrubberStatuses, collectorInputFileType, originEntryOutputPs);
}
return isValid;
}
protected boolean loadCollectorBatch(CollectorBatch batch, String fileName, int batchIndex, CollectorReportData collectorReportData, List<CollectorScrubberStatus> collectorScrubberStatuses, BatchInputFileType collectorInputFileType, PrintStream originEntryOutputPs) {
boolean isValid = true;
MessageMap messageMap = batch.getMessageMap();
// terminate if there were parse errors
if (messageMap.hasErrors()) {
isValid = false;
}
if (isValid) {
collectorReportData.setNumInputDetails(batch);
// check totals
isValid = checkTrailerTotals(batch, collectorReportData, messageMap);
}
// do validation, base collector files rules and total checks
if (isValid) {
isValid = performValidation(batch, messageMap);
}
if (isValid) {
// mark batch as valid
collectorReportData.markValidationStatus(batch, true);
prescrubParsedCollectorBatch(batch, collectorReportData);
String collectorFileDirectoryName = collectorInputFileType.getDirectoryPath();
// create a input file for scrubber
String collectorInputFileNameForScrubber = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_BACKUP_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
PrintStream inputFilePs = null;
try {
inputFilePs = new PrintStream(collectorInputFileNameForScrubber);
for (OriginEntryFull entry : batch.getOriginEntries()){
inputFilePs.printf("%s\n", entry.getLine());
}
} catch (IOException e) {
throw new RuntimeException("loadCollectorFile Stopped: " + e.getMessage(), e);
} finally {
IOUtils.closeQuietly(inputFilePs);
}
CollectorScrubberStatus collectorScrubberStatus = collectorScrubberService.scrub(batch, collectorReportData, collectorFileDirectoryName);
collectorScrubberStatuses.add(collectorScrubberStatus);
processInterDepartmentalBillingAmounts(batch);
// store origin group, entries, and collector detairs
String collectorDemergerOutputFileName = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_DEMERGER_VAILD_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
batch.setDefaultsAndStore(collectorReportData, collectorDemergerOutputFileName, originEntryOutputPs);
collectorReportData.incrementNumPersistedBatches();
}
else {
collectorReportData.incrementNumNonPersistedBatches();
collectorReportData.incrementNumNotPersistedOriginEntryRecords(batch.getOriginEntries().size());
collectorReportData.incrementNumNotPersistedCollectorDetailRecords(batch.getCollectorDetails().size());
// mark batch as invalid
collectorReportData.markValidationStatus(batch, false);
}
return isValid;
}
/**
* After a parse error, tries to go through the file to see if the email address can be determined. This method will not throw
* an exception.
*
* It's not doing much right now, just returning null
*
* @param fileName the name of the file that a parsing error occurred on
* @return the email from the file
*/
protected String attemptToParseEmailAfterParseError(String fileName) {
return null;
}
/**
* Calls batch input service to parse the xml contents into an object. Any errors will be contained in GlobalVariables.MessageMap
*
* @param fileName the name of the file to parse
* @param MessageMap a map of errors resultant from the parsing
* @return the CollectorBatch of details parsed from the file
*/
protected List<CollectorBatch> doCollectorFileParse(String fileName, MessageMap messageMap, BatchInputFileType collectorInputFileType, CollectorReportData collectorReportData) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(fileName);
}
catch (FileNotFoundException e) {
LOG.error("file to parse not found " + fileName, e);
collectorReportData.markUnparsableFileNames(fileName);
throw new RuntimeException("Cannot find the file requested to be parsed " + fileName + " " + e.getMessage(), e);
}
catch (RuntimeException e) {
collectorReportData.markUnparsableFileNames(fileName);
throw e;
}
List<CollectorBatch> parsedObject = null;
try {
byte[] fileByteContent = IOUtils.toByteArray(inputStream);
parsedObject = (List<CollectorBatch>) batchInputFileService.parse(collectorInputFileType, fileByteContent);
}
catch (IOException e) {
LOG.error("error while getting file bytes: " + e.getMessage(), e);
collectorReportData.markUnparsableFileNames(fileName);
throw new RuntimeException("Error encountered while attempting to get file bytes: " + e.getMessage(), e);
}
catch (ParseException e1) {
LOG.error("errors parsing file " + e1.getMessage(), e1);
collectorReportData.markUnparsableFileNames(fileName);
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML, new String[] { e1.getMessage() });
}
catch (RuntimeException e) {
collectorReportData.markUnparsableFileNames(fileName);
throw e;
}
return parsedObject;
}
protected void prescrubParsedCollectorBatch(CollectorBatch collectorBatch, CollectorReportData collectorReportData) {
if (preScrubberService.deriveChartOfAccountsCodeIfSpaces()) {
PreScrubberReportData preScrubberReportData = collectorReportData.getPreScrubberReportData();
int inputRecords = collectorBatch.getOriginEntries().size();
Set<String> noChartCodesCache = new HashSet<String>();
Set<String> multipleChartCodesCache = new HashSet<String>();
Map<String, String> accountNumberToChartCodeCache = new HashMap<String, String>();
Iterator<?> originEntryAndDetailIterator = IteratorUtils.chainedIterator(collectorBatch.getOriginEntries().iterator(), collectorBatch.getCollectorDetails().iterator());
while (originEntryAndDetailIterator.hasNext()) {
Object originEntryOrDetail = originEntryAndDetailIterator.next();
if (StringUtils.isBlank(extractChartOfAccountsCode(originEntryOrDetail))) {
String accountNumber = extractAccountNumber(originEntryOrDetail);
boolean nonExistent = false;
boolean multipleFound = false;
String chartOfAccountsCode = null;
if (noChartCodesCache.contains(accountNumber)) {
nonExistent = true;
}
else if (multipleChartCodesCache.contains(accountNumber)) {
multipleFound = true;
}
else if (accountNumberToChartCodeCache.containsKey(accountNumber)) {
chartOfAccountsCode = accountNumberToChartCodeCache.get(accountNumber);
}
else {
Collection<Account> accounts = accountService.getAccountsForAccountNumber(accountNumber);
if (accounts.size() == 1) {
chartOfAccountsCode = accounts.iterator().next().getChartOfAccountsCode();
accountNumberToChartCodeCache.put(accountNumber, chartOfAccountsCode);
}
else if (accounts.size() == 0) {
noChartCodesCache.add(accountNumber);
nonExistent = true;
}
else {
multipleChartCodesCache.add(accountNumber);
multipleFound = true;
}
}
if (!nonExistent && !multipleFound) {
setChartOfAccountsCode(originEntryOrDetail, chartOfAccountsCode);
}
}
}
preScrubberReportData.getAccountsWithMultipleCharts().addAll(multipleChartCodesCache);
preScrubberReportData.getAccountsWithNoCharts().addAll(noChartCodesCache);
preScrubberReportData.setInputRecords(preScrubberReportData.getInputRecords() + inputRecords);
preScrubberReportData.setOutputRecords(preScrubberReportData.getOutputRecords() + inputRecords);
}
}
protected String extractChartOfAccountsCode(Object originEntryOrDetail) {
if (originEntryOrDetail instanceof OriginEntryInformation)
return ((OriginEntryInformation) originEntryOrDetail).getChartOfAccountsCode();
return ((CollectorDetail) originEntryOrDetail).getChartOfAccountsCode();
}
protected String extractAccountNumber(Object originEntryOrDetail) {
if (originEntryOrDetail instanceof OriginEntryInformation)
return ((OriginEntryInformation) originEntryOrDetail).getAccountNumber();
return ((CollectorDetail) originEntryOrDetail).getAccountNumber();
}
protected void setChartOfAccountsCode(Object originEntryOrDetail, String chartOfAccountsCode) {
if (originEntryOrDetail instanceof OriginEntryInformation)
((OriginEntryInformation) originEntryOrDetail).setChartOfAccountsCode(chartOfAccountsCode);
else
((CollectorDetail) originEntryOrDetail).setChartOfAccountsCode(chartOfAccountsCode);
}
/**
* Validates the contents of a parsed file.
*
* @param batch - batch to validate
* @return boolean - true if validation was OK, false if there were errors
* @see org.kuali.kfs.gl.batch.service.CollectorHelperService#performValidation(org.kuali.kfs.gl.batch.CollectorBatch)
*/
public boolean performValidation(CollectorBatch batch) {
return performValidation(batch, GlobalVariables.getMessageMap());
}
/**
* Performs the following checks on the collector batch: Any errors will be contained in GlobalVariables.MessageMap
*
* @param batch - batch to validate
* @param MessageMap the map into which to put errors encountered during validation
* @return boolean - true if validation was successful, false it not
*/
protected boolean performValidation(CollectorBatch batch, MessageMap messageMap) {
boolean valid = performCollectorHeaderValidation(batch, messageMap);
performUppercasing(batch);
boolean performDuplicateHeaderCheck = parameterService.getParameterValueAsBoolean(CollectorStep.class, SystemGroupParameterNames.COLLECTOR_PERFORM_DUPLICATE_HEADER_CHECK);
if (valid && performDuplicateHeaderCheck) {
valid = duplicateHeaderCheck(batch, messageMap);
}
if (valid) {
valid = checkForMixedDocumentTypes(batch, messageMap);
}
if (valid) {
valid = checkForMixedBalanceTypes(batch, messageMap);
}
if (valid) {
valid = checkDetailKeys(batch, messageMap);
}
return valid;
}
/**
* Uppercases sub-account, sub-object, and project fields
*
* @param batch CollectorBatch with data to uppercase
*/
protected void performUppercasing(CollectorBatch batch) {
for (OriginEntryFull originEntry : batch.getOriginEntries()) {
if (StringUtils.isNotBlank(originEntry.getSubAccountNumber())) {
originEntry.setSubAccountNumber(originEntry.getSubAccountNumber().toUpperCase());
}
if (StringUtils.isNotBlank(originEntry.getFinancialSubObjectCode())) {
originEntry.setFinancialSubObjectCode(originEntry.getFinancialSubObjectCode().toUpperCase());
}
if (StringUtils.isNotBlank(originEntry.getProjectCode())) {
originEntry.setProjectCode(originEntry.getProjectCode().toUpperCase());
}
}
for (CollectorDetail detail : batch.getCollectorDetails()) {
if (StringUtils.isNotBlank(detail.getSubAccountNumber())) {
detail.setSubAccountNumber(detail.getSubAccountNumber().toUpperCase());
}
if (StringUtils.isNotBlank(detail.getFinancialSubObjectCode())) {
detail.setFinancialSubObjectCode(detail.getFinancialSubObjectCode().toUpperCase());
}
}
}
protected boolean performCollectorHeaderValidation(CollectorBatch batch, MessageMap messageMap) {
if (batch.isHeaderlessBatch()) {
// if it's a headerless batch, don't validate the header, but it's still an error
return false;
}
boolean valid = true;
if (StringUtils.isBlank(batch.getChartOfAccountsCode())) {
valid = false;
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_CHART_CODE_REQUIRED);
}
if (StringUtils.isBlank(batch.getOrganizationCode())) {
valid = false;
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_ORGANIZATION_CODE_REQUIRED);
}
if (StringUtils.isBlank(batch.getCampusCode())) {
valid = false;
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_CAMPUS_CODE_REQUIRED);
}
if (StringUtils.isBlank(batch.getPhoneNumber())) {
valid = false;
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_PHONE_NUMBER_REQUIRED);
}
if (StringUtils.isBlank(batch.getMailingAddress())) {
valid = false;
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_MAILING_ADDRESS_REQUIRED);
}
if (StringUtils.isBlank(batch.getDepartmentName())) {
valid = false;
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.HEADER_DEPARTMENT_NAME_REQUIRED);
}
return valid;
}
/**
* Modifies the amounts in the ID Billing Detail rows, depending on specific business rules. For this default implementation,
* see the {@link #negateAmountIfNecessary(InterDepartmentalBilling, BalanceTyp, ObjectType, CollectorBatch)} method to see how
* the billing detail amounts are modified.
*
* @param batch a CollectorBatch to process
*/
protected void processInterDepartmentalBillingAmounts(CollectorBatch batch) {
for (CollectorDetail collectorDetail : batch.getCollectorDetails()) {
String balanceTypeCode = getBalanceTypeCode(collectorDetail, batch);
BalanceType balanceTyp = new BalanceType();
balanceTyp.setFinancialBalanceTypeCode(balanceTypeCode);
balanceTyp = (BalanceType) SpringContext.getBean(BusinessObjectService.class).retrieve(balanceTyp);
if (balanceTyp == null) {
// no balance type in db
LOG.info("No balance type code found for ID billing record. " + collectorDetail);
continue;
}
collectorDetail.refreshReferenceObject(KFSPropertyConstants.FINANCIAL_OBJECT);
if (collectorDetail.getFinancialObject() == null) {
// no object code in db
LOG.info("No object code found for ID billing record. " + collectorDetail);
continue;
}
ObjectType objectType = collectorDetail.getFinancialObject().getFinancialObjectType();
/** Commented out for KULRNE-5922 */
// negateAmountIfNecessary(collectorDetail, balanceTyp, objectType, batch);
}
}
/**
* Negates the amount of the internal departmental billing detail record if necessary. For this default implementation, if the
* balance type's offset indicator is yes and the object type has a debit indicator, then the amount is negated.
*
* @param collectorDetail the collector detail
* @param balanceTyp the balance type
* @param objectType the object type
* @param batch the patch to which the interDepartmentalBilling parameter belongs
*/
protected void negateAmountIfNecessary(CollectorDetail collectorDetail, BalanceType balanceTyp, ObjectType objectType, CollectorBatch batch) {
if (balanceTyp != null && objectType != null) {
if (balanceTyp.isFinancialOffsetGenerationIndicator()) {
if (KFSConstants.GL_DEBIT_CODE.equals(objectType.getFinObjectTypeDebitcreditCd())) {
KualiDecimal amount = collectorDetail.getCollectorDetailItemAmount();
amount = amount.negated();
collectorDetail.setCollectorDetailItemAmount(amount);
}
}
}
}
/**
* Returns the balance type code for the interDepartmentalBilling record. This default implementation will look into the system
* parameters to determine the balance type
*
* @param interDepartmentalBilling a inter departmental billing detail record
* @param batch the batch to which the interDepartmentalBilling billing belongs
* @return the balance type code for the billing detail
*/
protected String getBalanceTypeCode(CollectorDetail collectorDetail, CollectorBatch batch) {
return collectorDetail.getFinancialBalanceTypeCode();
}
/**
* Checks header against previously loaded batch headers for a duplicate submission.
*
* @param batch - batch to check
* @return true if header if OK, false if header was used previously
*/
protected boolean duplicateHeaderCheck(CollectorBatch batch, MessageMap messageMap) {
boolean validHeader = true;
CollectorHeader foundHeader = batch.retrieveDuplicateHeader();
if (foundHeader != null) {
LOG.error("batch header was matched to a previously loaded batch");
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.DUPLICATE_BATCH_HEADER);
validHeader = false;
}
return validHeader;
}
/**
* Iterates through the origin entries and builds a map on the document types. Then checks there was only one document type
* found.
*
* @param batch - batch to check document types
* @return true if there is only one document type, false if multiple document types were found.
*/
protected boolean checkForMixedDocumentTypes(CollectorBatch batch, MessageMap messageMap) {
boolean docTypesNotMixed = true;
Set<String> batchDocumentTypes = new HashSet<String>();
for (OriginEntryFull entry : batch.getOriginEntries()) {
batchDocumentTypes.add(entry.getFinancialDocumentTypeCode());
}
if (batchDocumentTypes.size() > 1) {
LOG.error("mixed document types found in batch");
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.MIXED_DOCUMENT_TYPES);
docTypesNotMixed = false;
}
return docTypesNotMixed;
}
/**
* Iterates through the origin entries and builds a map on the balance types. Then checks there was only one balance type found.
*
* @param batch - batch to check balance types
* @return true if there is only one balance type, false if multiple balance types were found
*/
protected boolean checkForMixedBalanceTypes(CollectorBatch batch, MessageMap messageMap) {
boolean balanceTypesNotMixed = true;
Set<String> balanceTypes = new HashSet<String>();
for (OriginEntryFull entry : batch.getOriginEntries()) {
balanceTypes.add(entry.getFinancialBalanceTypeCode());
}
if (balanceTypes.size() > 1) {
LOG.error("mixed balance types found in batch");
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.MIXED_BALANCE_TYPES);
balanceTypesNotMixed = false;
}
return balanceTypesNotMixed;
}
/**
* Verifies each detail (id billing) record key has an corresponding gl entry in the same batch. The key is built by joining the
* values of chart of accounts code, account number, sub account number, object code, and sub object code.
*
* @param batch - batch to validate
* @return true if all detail records had matching keys, false otherwise
*/
protected boolean checkDetailKeys(CollectorBatch batch, MessageMap messageMap) {
boolean detailKeysFound = true;
// build a Set of keys from the gl entries to compare with
Set<String> glEntryKeys = new HashSet<String>();
for (OriginEntryFull entry : batch.getOriginEntries()) {
glEntryKeys.add(generateOriginEntryMatchingKey(entry, ", "));
}
for (CollectorDetail collectorDetail : batch.getCollectorDetails()) {
String collectorDetailKey = generateCollectorDetailMatchingKey(collectorDetail, ", ");
if (!glEntryKeys.contains(collectorDetailKey)) {
LOG.error("found detail key without a matching gl entry key " + collectorDetailKey);
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.NONMATCHING_DETAIL_KEY, collectorDetailKey);
detailKeysFound = false;
}
}
return detailKeysFound;
}
/**
* Generates a String representation of the OriginEntryFull's primary key
*
* @param entry origin entry to get key from
* @param delimiter the String delimiter to separate parts of the key
* @return the key as a String
*/
protected String generateOriginEntryMatchingKey(OriginEntryFull entry, String delimiter) {
return StringUtils.join(new String[] { ObjectUtils.isNull(entry.getUniversityFiscalYear()) ? "" : entry.getUniversityFiscalYear().toString(), entry.getUniversityFiscalPeriodCode(), entry.getChartOfAccountsCode(), entry.getAccountNumber(), entry.getSubAccountNumber(), entry.getFinancialObjectCode(), entry.getFinancialSubObjectCode(), entry.getFinancialObjectTypeCode(), entry.getDocumentNumber(), entry.getFinancialDocumentTypeCode(), entry.getFinancialSystemOriginationCode() }, delimiter);
}
/**
* Generates a String representation of the CollectorDetail's primary key
*
* @param collectorDetail collector detail to get key from
* @param delimiter the String delimiter to separate parts of the key
* @return the key as a String
*/
protected String generateCollectorDetailMatchingKey(CollectorDetail collectorDetail, String delimiter) {
return StringUtils.join(new String[] { ObjectUtils.isNull(collectorDetail.getUniversityFiscalYear()) ? "" : collectorDetail.getUniversityFiscalYear().toString(), collectorDetail.getUniversityFiscalPeriodCode(), collectorDetail.getChartOfAccountsCode(), collectorDetail.getAccountNumber(), collectorDetail.getSubAccountNumber(), collectorDetail.getFinancialObjectCode(), collectorDetail.getFinancialSubObjectCode(), collectorDetail.getFinancialObjectTypeCode(), collectorDetail.getDocumentNumber(), collectorDetail.getFinancialDocumentTypeCode(), collectorDetail.getFinancialSystemOriginationCode() }, delimiter);
}
/**
* Checks the batch total line count and amounts against the trailer. Any errors will be contained in GlobalVariables.MessageMap
*
* @param batch batch to check totals for
* @param collectorReportData collector report data (optional)
* @see org.kuali.kfs.gl.batch.service.CollectorHelperService#checkTrailerTotals(org.kuali.kfs.gl.batch.CollectorBatch,
* org.kuali.kfs.gl.report.CollectorReportData)
*/
public boolean checkTrailerTotals(CollectorBatch batch, CollectorReportData collectorReportData) {
return checkTrailerTotals(batch, collectorReportData, GlobalVariables.getMessageMap());
}
/**
* Checks the batch total line count and amounts against the trailer. Any errors will be contained in GlobalVariables.MessageMap
*
* @param batch - batch to check totals for
* @return boolean - true if validation was successful, false it not
*/
protected boolean checkTrailerTotals(CollectorBatch batch, CollectorReportData collectorReportData, MessageMap messageMap) {
boolean trailerTotalsMatch = true;
int actualRecordCount = batch.getOriginEntries().size() + batch.getCollectorDetails().size();
if (actualRecordCount != batch.getTotalRecords()) {
LOG.error("trailer check on total count did not pass, expected count: " + String.valueOf(batch.getTotalRecords()) + ", actual count: " + String.valueOf(actualRecordCount));
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.TRAILER_ERROR_COUNTNOMATCH, String.valueOf(batch.getTotalRecords()), String.valueOf(actualRecordCount));
trailerTotalsMatch = false;
}
OriginEntryTotals totals = batch.getOriginEntryTotals();
if (batch.getOriginEntries().size() == 0) {
if (!KualiDecimal.ZERO.equals(batch.getTotalAmount())) {
LOG.error("trailer total should be zero when there are no origin entries");
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.TRAILER_ERROR_AMOUNT_SHOULD_BE_ZERO);
}
return false;
}
// retrieve document types that balance by equal debits and credits
Collection<String> documentTypes = new ArrayList<String>( parameterService.getParameterValuesAsString(CollectorStep.class, KFSConstants.SystemGroupParameterNames.COLLECTOR_EQUAL_DC_TOTAL_DOCUMENT_TYPES) );
boolean equalDebitCreditTotal = false;
for ( String documentType : documentTypes ) {
documentType = StringUtils.remove(documentType, "*").toUpperCase();
if (batch.getOriginEntries().get(0).getFinancialDocumentTypeCode().startsWith(documentType)
&& KFSConstants.BALANCE_TYPE_ACTUAL.equals(batch.getOriginEntries().get(0).getFinancialBalanceTypeCode())) {
equalDebitCreditTotal = true;
}
}
if (equalDebitCreditTotal) {
// credits must equal debits must equal total trailer amount
if (!totals.getCreditAmount().equals(totals.getDebitAmount()) || !totals.getCreditAmount().equals(batch.getTotalAmount())) {
LOG.error("trailer check on total amount did not pass, debit should equal credit, should equal trailer total");
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.TRAILER_ERROR_AMOUNTNOMATCH1, totals.getCreditAmount().toString(), totals.getDebitAmount().toString(), batch.getTotalAmount().toString());
trailerTotalsMatch = false;
}
}
else {
// credits plus debits plus other amount must equal trailer
KualiDecimal totalGlEntries = totals.getCreditAmount().add(totals.getDebitAmount()).add(totals.getOtherAmount());
if (!totalGlEntries.equals(batch.getTotalAmount())) {
LOG.error("trailer check on total amount did not pass, sum of gl entry amounts should equal trailer total");
messageMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.Collector.TRAILER_ERROR_AMOUNTNOMATCH2, totalGlEntries.toString(), batch.getTotalAmount().toString());
trailerTotalsMatch = false;
}
}
return trailerTotalsMatch;
}
public void setCollectorDetailService(CollectorDetailService collectorDetailService) {
this.collectorDetailService = collectorDetailService;
}
public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) {
this.originEntryGroupService = originEntryGroupService;
}
public void setOriginEntryService(OriginEntryService originEntryService) {
this.originEntryService = originEntryService;
}
/**
* Returns the name of the directory where Collector files are saved
*
* @return the name of the staging directory
*/
public String getStagingDirectory() {
return configurationService.getPropertyValueAsString(KFSConstants.GL_COLLECTOR_STAGING_DIRECTORY);
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
this.batchInputFileService = batchInputFileService;
}
/**
* Sets the collectorScrubberService attribute value.
*
* @param collectorScrubberService The collectorScrubberService to set.
*/
public void setCollectorScrubberService(CollectorScrubberService collectorScrubberService) {
this.collectorScrubberService = collectorScrubberService;
}
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
/**
* Sets the batchFileDirectoryName attribute value.
* @param batchFileDirectoryName The batchFileDirectoryName to set.
*/
public void setBatchFileDirectoryName(String batchFileDirectoryName) {
this.batchFileDirectoryName = batchFileDirectoryName;
}
/**
* Sets the accountService attribute value.
* @param accountService The accountService to set.
*/
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
/**
* Sets the preScrubberService attribute value.
* @param preScrubberService The preScrubberService to set.
*/
public void setPreScrubberService(PreScrubberService preScrubberService) {
this.preScrubberService = preScrubberService;
}
}