/*
* 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.service.impl;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.coa.businessobject.ProjectCode;
import org.kuali.kfs.coa.businessobject.SubAccount;
import org.kuali.kfs.coa.businessobject.SubObjectCode;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.coa.service.ObjectCodeService;
import org.kuali.kfs.coa.service.SubAccountService;
import org.kuali.kfs.coa.service.SubObjectCodeService;
import org.kuali.kfs.pdp.PdpConstants;
import org.kuali.kfs.pdp.PdpKeyConstants;
import org.kuali.kfs.pdp.PdpParameterConstants;
import org.kuali.kfs.pdp.PdpPropertyConstants;
import org.kuali.kfs.pdp.businessobject.AccountingChangeCode;
import org.kuali.kfs.pdp.businessobject.CustomerProfile;
import org.kuali.kfs.pdp.businessobject.PayeeType;
import org.kuali.kfs.pdp.businessobject.PaymentAccountDetail;
import org.kuali.kfs.pdp.businessobject.PaymentAccountHistory;
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.PaymentStatus;
import org.kuali.kfs.pdp.dataaccess.PaymentFileLoadDao;
import org.kuali.kfs.pdp.service.CustomerProfileService;
import org.kuali.kfs.pdp.service.PaymentFileValidationService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.businessobject.OriginationCode;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.OriginationCodeService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.doctype.DocumentTypeService;
import org.kuali.rice.krad.bo.KualiCodeBase;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DataDictionaryService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
/**
* @see org.kuali.kfs.pdp.batch.service.PaymentFileValidationService
*/
@Transactional
public class PaymentFileValidationServiceImpl implements PaymentFileValidationService {
public static final String[] PAYMENT_GROUP_PROPERTIES_TO_CHECK_MAX_LENGTH = {"line1Address", "line2Address", "line3Address", "line4Address", "city", "state", "country", "zipCd", "adviceEmailAddress"};
protected CustomerProfileService customerProfileService;
protected PaymentFileLoadDao paymentFileLoadDao;
protected ParameterService parameterService;
protected ConfigurationService kualiConfigurationService;
protected DateTimeService dateTimeService;
protected AccountService accountService;
protected SubAccountService subAccountService;
protected ObjectCodeService objectCodeService;
protected SubObjectCodeService subObjectCodeService;
protected BankService bankService;
protected OriginationCodeService originationCodeService;
protected DocumentTypeService documentTypeService;
protected BusinessObjectService businessObjectService;
protected DataDictionaryService dataDictionaryService;
/**
* @see org.kuali.kfs.pdp.batch.service.PaymentFileValidationService#doHardEdits(org.kuali.kfs.pdp.businessobject.PaymentFile,
* org.kuali.rice.krad.util.MessageMap)
*/
@Override
public void doHardEdits(PaymentFileLoad paymentFile, MessageMap errorMap) {
processHeaderValidation(paymentFile, errorMap);
if (errorMap.hasNoErrors()) {
processGroupValidation(paymentFile, errorMap);
}
if (errorMap.hasNoErrors()) {
processTrailerValidation(paymentFile, errorMap);
}
}
/**
* Validates payment file header fields <li>Checks customer exists in customer profile table and is active</li>
*
* @param paymentFile payment file object
* @param errorMap map in which errors will be added to
*/
protected void processHeaderValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
if (customer == null) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_CUSTOMER, paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
}
else {
if (!customer.isActive()) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_CUSTOMER, paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
}
else {
paymentFile.setCustomer(customer);
}
}
}
/**
* Validates payment file trailer fields <li>Reconciles actual to expected payment count and totals</li> <li>Verifies the batch
* is not a duplicate</li>
*
* @param paymentFile payment file object
* @param errorMap map in which errors will be added to
*/
protected void processTrailerValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
// compare trailer payment count to actual count loaded
if (paymentFile.getActualPaymentCount() != paymentFile.getPaymentCount()) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYMENT_COUNT_MISMATCH, Integer.toString(paymentFile.getPaymentCount()), Integer.toString(paymentFile.getActualPaymentCount()));
}
// compare trailer total amount with actual total amount
if (paymentFile.getCalculatedPaymentTotalAmount().compareTo(paymentFile.getPaymentTotalAmount()) != 0) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYMENT_TOTAL_MISMATCH, paymentFile.getPaymentTotalAmount().toString(), paymentFile.getCalculatedPaymentTotalAmount().toString());
}
// Check to see if this is a duplicate batch
Timestamp now = new Timestamp(paymentFile.getCreationDate().getTime());
if (paymentFileLoadDao.isDuplicateBatch(paymentFile.getCustomer(), paymentFile.getPaymentCount(), paymentFile.getPaymentTotalAmount().bigDecimalValue(), now)) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DUPLICATE_BATCH);
}
}
/**
* Validates payment file groups <li>Checks number of note lines needed is not above the configured maximum allowed</li> <li>
* Verifies group total is not negative</li> <li>Verifies detail accounting total equals net payment amount</li>
*
* @param paymentFile payment file object
* @param errorMap map in which errors will be added to
*/
protected void processGroupValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
int groupCount = 0;
for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
groupCount++;
int noteLineCount = 0;
int detailCount = 0;
// We've encountered Payment Files that have address lines exceeding the column size in DB table;
// so adding extra validation on payment group BO, especially the max length, based on DD definitions.
// Check that PaymentGroup String properties don't exceed maximum allowed length
checkPaymentGroupPropertyMaxLength(paymentGroup, errorMap);
// verify payee id and owner code if customer requires them to be filled in
if (paymentFile.getCustomer().getPayeeIdRequired() && StringUtils.isBlank(paymentGroup.getPayeeId())) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_ID_REQUIRED, Integer.toString(groupCount));
}
if (paymentFile.getCustomer().getOwnershipCodeRequired() && StringUtils.isBlank(paymentGroup.getPayeeOwnerCd())) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_OWNER_CODE, Integer.toString(groupCount));
}
// validate payee id type
if (StringUtils.isNotBlank(paymentGroup.getPayeeIdTypeCd())) {
PayeeType payeeType = businessObjectService.findBySinglePrimaryKey(PayeeType.class, paymentGroup.getPayeeIdTypeCd());
if (payeeType == null) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_PAYEE_ID_TYPE, Integer.toString(groupCount), paymentGroup.getPayeeIdTypeCd());
}
}
// validate bank
String bankCode = paymentGroup.getBankCode();
if (StringUtils.isNotBlank(bankCode)) {
Bank bank = bankService.getByPrimaryId(bankCode);
if (bank == null) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_BANK_CODE, Integer.toString(groupCount), bankCode);
}
else if (!bank.isActive()) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_BANK_CODE, Integer.toString(groupCount), bankCode);
}
}
KualiDecimal groupTotal = KualiDecimal.ZERO;
for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
detailCount++;
noteLineCount++; // Add a line to print the invoice number
noteLineCount = noteLineCount + paymentDetail.getNotes().size();
if ((paymentDetail.getNetPaymentAmount() == null) && (!paymentDetail.isDetailAmountProvided())) {
paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
}
else if ((paymentDetail.getNetPaymentAmount() == null) && (paymentDetail.isDetailAmountProvided())) {
paymentDetail.setNetPaymentAmount(paymentDetail.getCalculatedPaymentAmount());
}
// compare net to accounting segments
if (paymentDetail.getAccountTotal().compareTo(paymentDetail.getNetPaymentAmount()) != 0) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DETAIL_TOTAL_MISMATCH, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getAccountTotal().toString(), paymentDetail.getNetPaymentAmount().toString());
}
// validate origin code if given
if (StringUtils.isNotBlank(paymentDetail.getFinancialSystemOriginCode())) {
OriginationCode originationCode = originationCodeService.getByPrimaryKey(paymentDetail.getFinancialSystemOriginCode());
if (originationCode == null) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_ORIGIN_CODE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialSystemOriginCode());
}
}
// validate doc type if given
if (StringUtils.isNotBlank(paymentDetail.getFinancialDocumentTypeCode())) {
if ( !documentTypeService.isActiveByName(paymentDetail.getFinancialDocumentTypeCode()) ) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_DOC_TYPE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialDocumentTypeCode());
}
}
groupTotal = groupTotal.add(paymentDetail.getNetPaymentAmount());
}
// verify total for group is not negative
if (groupTotal.doubleValue() < 0) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_NEGATIVE_GROUP_TOTAL, Integer.toString(groupCount));
}
// check that the number of detail items and note lines will fit on a check stub
if (noteLineCount > getMaxNoteLines()) {
errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_MAX_NOTE_LINES, Integer.toString(groupCount), Integer.toString(noteLineCount), Integer.toString(getMaxNoteLines()));
}
}
}
/**
* Checks the max length for PaymentGroup properties that could possibly exceed the maximum length defined in DD.
* This checking is needed because we've encountered Payment Files that have address lines exceeding the column size in DB table, and this causes
* SQL exceptions while saving PaymentGroup. So we need check all PaymentGroup property values which might exceed the maximum length allowed.
*
* @param paymentGroup the payment group for which field lengths are checked
* @param errorMap map in which errors will be added to
*/
protected void checkPaymentGroupPropertyMaxLength(PaymentGroup paymentGroup, MessageMap errorMap) {
for (String propertyName : PAYMENT_GROUP_PROPERTIES_TO_CHECK_MAX_LENGTH) {
// we only check max length on String type properties
String propertyValue = (String)ObjectUtils.getPropertyValue(paymentGroup, propertyName);
if (StringUtils.isNotEmpty(propertyValue)) {
// we assume that max length defined in DD is the same as the size of the column in PaymentGroup table
Integer maxLength = dataDictionaryService.getAttributeMaxLength(PaymentGroup.class, propertyName);
if ((maxLength != null) && (maxLength.intValue() < propertyValue.length())) {
String errorLabel = dataDictionaryService.getAttributeErrorLabel(PaymentGroup.class, propertyName);
errorLabel += " with the value '" + propertyValue + "'";
errorMap.putError(KFSConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_MAX_LENGTH, new String[]{errorLabel, maxLength.toString()});
}
}
}
}
/**
* @see org.kuali.kfs.pdp.service.PaymentFileValidationService#doSoftEdits(org.kuali.kfs.pdp.businessobject.PaymentFile)
*/
@Override
public List<String> doSoftEdits(PaymentFileLoad paymentFile) {
List<String> warnings = new ArrayList<String>();
CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
// check payment amount does not exceed the configured threshold amount of this customer
if (paymentFile.getPaymentTotalAmount().compareTo(customer.getFileThresholdAmount()) > 0) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_FILE_THRESHOLD, paymentFile.getPaymentTotalAmount().toString(), customer.getFileThresholdAmount().toString());
paymentFile.setFileThreshold(true);
}
processGroupSoftEdits(paymentFile, customer, warnings);
return warnings;
}
/**
* Set defaults for group fields and do tax checks.
*
* @param paymentFile payment file object
* @param customer payment customer
* @param warnings <code>List</code> list of accumulated warning messages
*/
public void processGroupSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, List<String> warnings) {
PaymentStatus openStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.OPEN);
for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
paymentGroup.setBatchId(paymentFile.getBatchId());
paymentGroup.setPaymentStatusCode(openStatus.getCode());
paymentGroup.setPaymentStatus(openStatus);
paymentGroup.setPayeeName(paymentGroup.getPayeeName().toUpperCase());
// Set defaults for missing information
defaultGroupIndicators(paymentGroup);
// Tax Group Requirements for automatic Holding
checkForTaxEmailRequired(paymentFile, paymentGroup, customer);
// KFSMI-9997 / KFSMI-9998
// Checks for valid payment date or set to tomorrow if missing
checkGroupPaymentDate(paymentGroup, warnings);
// do edits on detail lines
for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
paymentDetail.setPaymentGroupId(paymentGroup.getId());
processDetailSoftEdits(paymentFile, customer, paymentDetail, warnings);
}
}
}
/**
* Set default fields on detail line and check amount against customer threshold.
*
* @param paymentFile payment file object
* @param customer payment customer
* @param paymentDetail <code>PaymentDetail</code> object to process
* @param warnings <code>List</code> list of accumulated warning messages
*/
protected void processDetailSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, PaymentDetail paymentDetail, List<String> warnings) {
updateDetailAmounts(paymentDetail);
// Check net payment amount
KualiDecimal testAmount = paymentDetail.getNetPaymentAmount();
if (testAmount.compareTo(customer.getPaymentThresholdAmount()) > 0) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_DETAIL_THRESHOLD, testAmount.toString(), customer.getPaymentThresholdAmount().toString());
paymentFile.setDetailThreshold(true);
paymentFile.getThresholdPaymentDetails().add(paymentDetail);
}
// set invoice date if it doesn't exist
if (paymentDetail.getInvoiceDate() == null) {
paymentDetail.setInvoiceDate(dateTimeService.getCurrentSqlDate());
}
if (paymentDetail.getPrimaryCancelledPayment() == null) {
paymentDetail.setPrimaryCancelledPayment(Boolean.FALSE);
}
// do accounting edits
for (PaymentAccountDetail paymentAccountDetail : paymentDetail.getAccountDetail()) {
paymentAccountDetail.setPaymentDetailId(paymentDetail.getId());
processAccountSoftEdits(paymentFile, customer, paymentAccountDetail, warnings);
}
}
/**
* Set default fields on account line and perform account field existence checks
*
* @param paymentFile payment file object
* @param customer payment customer
* @param paymentAccountDetail <code>PaymentAccountDetail</code> object to process
* @param warnings <code>List</code> list of accumulated warning messages
*/
protected void processAccountSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, PaymentAccountDetail paymentAccountDetail, List<String> warnings) {
List<PaymentAccountHistory> changeRecords = paymentAccountDetail.getAccountHistory();
// uppercase chart
paymentAccountDetail.setFinChartCode(paymentAccountDetail.getFinChartCode().toUpperCase());
// only do accounting edits if required by customer
if (customer.getAccountingEditRequired()) {
// check account number
Account account = accountService.getByPrimaryId(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr());
if (account == null) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_ACCOUNT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr());
KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_ACCOUNT);
replaceAccountingString(objChangeCd, changeRecords, customer, paymentAccountDetail);
}
else {
// check sub account code
if (StringUtils.isNotBlank(paymentAccountDetail.getSubAccountNbr())) {
SubAccount subAccount = subAccountService.getByPrimaryId(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getSubAccountNbr());
if (subAccount == null) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_SUB_ACCOUNT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getSubAccountNbr());
KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_SUB_ACCOUNT);
changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_ACCOUNT_DB_COLUMN_NAME, KFSConstants.getDashSubAccountNumber(), paymentAccountDetail.getSubAccountNbr(), objChangeCd));
paymentAccountDetail.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
}
}
// check object code
ObjectCode objectCode = objectCodeService.getByPrimaryIdForCurrentYear(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getFinObjectCode());
if (objectCode == null) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_OBJECT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getFinObjectCode());
KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_OBJECT);
replaceAccountingString(objChangeCd, changeRecords, customer, paymentAccountDetail);
}
// check sub object code
else if (StringUtils.isNotBlank(paymentAccountDetail.getFinSubObjectCode())) {
SubObjectCode subObjectCode = subObjectCodeService.getByPrimaryIdForCurrentYear(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getFinObjectCode(), paymentAccountDetail.getFinSubObjectCode());
if (subObjectCode == null) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_SUB_OBJECT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getFinObjectCode(), paymentAccountDetail.getFinSubObjectCode());
KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_SUB_OBJECT);
changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_OBJECT_DB_COLUMN_NAME, KFSConstants.getDashFinancialSubObjectCode(), paymentAccountDetail.getFinSubObjectCode(), objChangeCd));
paymentAccountDetail.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
}
}
}
// check project code
if (StringUtils.isNotBlank(paymentAccountDetail.getProjectCode())) {
ProjectCode projectCode = businessObjectService.findBySinglePrimaryKey(ProjectCode.class, paymentAccountDetail.getProjectCode());
if (projectCode == null) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_PROJECT, paymentAccountDetail.getProjectCode());
KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_PROJECT);
changeRecords.add(newAccountHistory(PdpPropertyConstants.PROJECT_DB_COLUMN_NAME, KFSConstants.getDashProjectCode(), paymentAccountDetail.getProjectCode(), objChangeCd));
paymentAccountDetail.setProjectCode(KFSConstants.getDashProjectCode());
}
}
}
// change nulls into ---'s for the fields that need it
if (StringUtils.isBlank(paymentAccountDetail.getFinSubObjectCode())) {
paymentAccountDetail.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
}
if (StringUtils.isBlank(paymentAccountDetail.getSubAccountNbr())) {
paymentAccountDetail.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
}
if (StringUtils.isBlank(paymentAccountDetail.getProjectCode())) {
paymentAccountDetail.setProjectCode(KFSConstants.getDashProjectCode());
}
}
/**
* Replaces the entire accounting string with defaults from the customer profile.
*
* @param objChangeCd code indicating reason for change
* @param changeRecords <code>List</code> of <code>PaymentAccountHistory</code> records
* @param customer profile of payment customer
* @param paymentAccountDetail account detail record
*/
protected void replaceAccountingString(KualiCodeBase objChangeCd, List<PaymentAccountHistory> changeRecords, CustomerProfile customer, PaymentAccountDetail paymentAccountDetail) {
changeRecords.add(newAccountHistory(PdpPropertyConstants.CHART_DB_COLUMN_NAME, customer.getDefaultChartCode(), paymentAccountDetail.getFinChartCode(), objChangeCd));
changeRecords.add(newAccountHistory(PdpPropertyConstants.ACCOUNT_DB_COLUMN_NAME, customer.getDefaultAccountNumber(), paymentAccountDetail.getAccountNbr(), objChangeCd));
changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_ACCOUNT_DB_COLUMN_NAME, customer.getDefaultSubAccountNumber(), paymentAccountDetail.getSubAccountNbr(), objChangeCd));
changeRecords.add(newAccountHistory(PdpPropertyConstants.OBJECT_DB_COLUMN_NAME, customer.getDefaultObjectCode(), paymentAccountDetail.getFinObjectCode(), objChangeCd));
changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_OBJECT_DB_COLUMN_NAME, customer.getDefaultSubObjectCode(), paymentAccountDetail.getFinSubObjectCode(), objChangeCd));
paymentAccountDetail.setFinChartCode(customer.getDefaultChartCode());
paymentAccountDetail.setAccountNbr(customer.getDefaultAccountNumber());
if (StringUtils.isNotBlank(customer.getDefaultSubAccountNumber())) {
paymentAccountDetail.setSubAccountNbr(customer.getDefaultSubAccountNumber());
}
else {
paymentAccountDetail.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
}
paymentAccountDetail.setFinObjectCode(customer.getDefaultObjectCode());
if (StringUtils.isNotBlank(customer.getDefaultSubAccountNumber())) {
paymentAccountDetail.setFinSubObjectCode(customer.getDefaultSubObjectCode());
}
else {
paymentAccountDetail.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
}
}
/**
* Helper method to construct a new payment account history record
*
* @param attName name of field that has changed
* @param newValue new value for the field
* @param oldValue field value that was changed
* @param changeCode code indicating reason for change
* @return <code>PaymentAccountHistory</code>
*/
protected PaymentAccountHistory newAccountHistory(String attName, String newValue, String oldValue, KualiCodeBase changeCode) {
PaymentAccountHistory paymentAccountHistory = new PaymentAccountHistory();
paymentAccountHistory.setAcctAttributeName(attName);
paymentAccountHistory.setAcctAttributeNewValue(newValue);
paymentAccountHistory.setAcctAttributeOrigValue(oldValue);
paymentAccountHistory.setAcctChangeDate(dateTimeService.getCurrentTimestamp());
paymentAccountHistory.setAccountingChange((AccountingChangeCode) changeCode);
return paymentAccountHistory;
}
/**
* Sets null amount fields to 0
*
* @param paymentDetail <code>PaymentDetail</code> to update
*/
protected void updateDetailAmounts(PaymentDetail paymentDetail) {
KualiDecimal zero = KualiDecimal.ZERO;
if (paymentDetail.getInvTotDiscountAmount() == null) {
paymentDetail.setInvTotDiscountAmount(zero);
}
if (paymentDetail.getInvTotShipAmount() == null) {
paymentDetail.setInvTotShipAmount(zero);
}
if (paymentDetail.getInvTotOtherDebitAmount() == null) {
paymentDetail.setInvTotOtherDebitAmount(zero);
}
if (paymentDetail.getInvTotOtherCreditAmount() == null) {
paymentDetail.setInvTotOtherCreditAmount(zero);
}
// update the total payment amount with the amount from the accounts if null
if (paymentDetail.getNetPaymentAmount() == null) {
paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
}
if (paymentDetail.getOrigInvoiceAmount() == null) {
KualiDecimal amt = paymentDetail.getNetPaymentAmount();
amt = amt.add(paymentDetail.getInvTotDiscountAmount());
amt = amt.subtract(paymentDetail.getInvTotShipAmount());
amt = amt.subtract(paymentDetail.getInvTotOtherDebitAmount());
amt = amt.add(paymentDetail.getInvTotOtherCreditAmount());
paymentDetail.setOrigInvoiceAmount(amt);
}
}
/**
* Sets null indicators to false
*
* @param paymentGroup <code>PaymentGroup</code> to update
*/
protected void defaultGroupIndicators(PaymentGroup paymentGroup) {
// If combineGroups isn't specified, we want it to default to true
if (paymentGroup.getCombineGroups() == null) {
paymentGroup.setCombineGroups(Boolean.TRUE);
}
if (paymentGroup.getCampusAddress() == null) {
paymentGroup.setCampusAddress(Boolean.FALSE);
}
if (paymentGroup.getPymtAttachment() == null) {
paymentGroup.setPymtAttachment(Boolean.FALSE);
}
if (paymentGroup.getPymtSpecialHandling() == null) {
paymentGroup.setPymtSpecialHandling(Boolean.FALSE);
}
if (paymentGroup.getProcessImmediate() == null) {
paymentGroup.setProcessImmediate(Boolean.FALSE);
}
if (paymentGroup.getEmployeeIndicator() == null) {
paymentGroup.setEmployeeIndicator(Boolean.FALSE);
}
if (paymentGroup.getNraPayment() == null) {
paymentGroup.setNraPayment(Boolean.FALSE);
}
if (paymentGroup.getTaxablePayment() == null) {
paymentGroup.setTaxablePayment(Boolean.FALSE);
}
}
/**
* Checks whether payment status should be set to held and a tax email sent indicating so
*
* @param paymentFile payment file object
* @param paymentGroup <code>PaymentGroup</code> being checked
* @param customer payment customer
*/
protected void checkForTaxEmailRequired(PaymentFileLoad paymentFile, PaymentGroup paymentGroup, CustomerProfile customer) {
PaymentStatus heldForNRAEmployee = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_NRA_EMPL_CD);
PaymentStatus heldForEmployee = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_EMPLOYEE_CD);
PaymentStatus heldForNRA = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_NRA_CD);
if (customer.getNraReview() && customer.getEmployeeCheck() && paymentGroup.getEmployeeIndicator().booleanValue() && paymentGroup.getNraPayment().booleanValue()) {
paymentGroup.setPaymentStatus(heldForNRAEmployee);
paymentFile.setTaxEmailRequired(true);
}
else if (customer.getEmployeeCheck() && paymentGroup.getEmployeeIndicator().booleanValue()) {
paymentGroup.setPaymentStatus(heldForEmployee);
paymentFile.setTaxEmailRequired(true);
}
else if (customer.getNraReview() && paymentGroup.getNraPayment().booleanValue()) {
paymentGroup.setPaymentStatus(heldForNRA);
paymentFile.setTaxEmailRequired(true);
}
}
/**
* Checks the payment date is not more than 30 days past or 30 days coming
*
* @param paymentGroup <code>PaymentGroup</code> being checked
* @param warnings <code>List</code> list of accumulated warning messages
*/
protected void checkGroupPaymentDate(PaymentGroup paymentGroup, List<String> warnings) {
Timestamp now = dateTimeService.getCurrentTimestamp();
Calendar nowPlus30 = Calendar.getInstance();
nowPlus30.setTime(now);
nowPlus30.add(Calendar.DATE, 30);
Calendar nowMinus30 = Calendar.getInstance();
nowMinus30.setTime(now);
nowMinus30.add(Calendar.DATE, -30);
if (paymentGroup.getPaymentDate() != null) {
Calendar payDate = Calendar.getInstance();
payDate.setTime(paymentGroup.getPaymentDate());
if (payDate.before(nowMinus30)) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_PAYDATE_OVER_30_DAYS_PAST, dateTimeService.toDateString(paymentGroup.getPaymentDate()));
}
if (payDate.after(nowPlus30)) {
addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_PAYDATE_OVER_30_DAYS_OUT, dateTimeService.toDateString(paymentGroup.getPaymentDate()));
}
}
else {
// KFSMI-9997
// Calculate tomorrow's date to set as payment date rather than null
Calendar tomorrow = Calendar.getInstance();
tomorrow.setTime(now);
tomorrow.add(Calendar.DATE, 1);
tomorrow.getTime();
Date paymentDate = new Date(tomorrow.getTime().getTime());
paymentGroup.setPaymentDate(paymentDate);
}
}
/**
* @return system parameter value giving the maximum number of notes allowed.
*/
protected int getMaxNoteLines() {
String maxLines = parameterService.getParameterValueAsString(KfsParameterConstants.PRE_DISBURSEMENT_ALL.class, PdpParameterConstants.MAX_NOTE_LINES);
if (StringUtils.isBlank(maxLines)) {
throw new RuntimeException("System parameter for max note lines is blank");
}
return Integer.parseInt(maxLines);
}
/**
* Helper method for substituting message parameters and adding the message to the warning list.
*
* @param warnings <code>List</code> of messages to add to
* @param messageKey resource key for message
* @param arguments message substitute parameters
*/
protected void addWarningMessage(List<String> warnings, String messageKey, String... arguments) {
// Add to global warnings so they will show up on the Payment File Batch Upload screen if
// the payment file was loaded via that screen
GlobalVariables.getMessageMap().putWarning(KFSConstants.GLOBAL_MESSAGES, messageKey, arguments);
String message = kualiConfigurationService.getPropertyValueAsString(messageKey);
warnings.add(MessageFormat.format(message, (Object[]) arguments));
}
/**
* Sets the customerProfileService attribute value.
*
* @param customerProfileService The customerProfileService to set.
*/
public void setCustomerProfileService(CustomerProfileService customerProfileService) {
this.customerProfileService = customerProfileService;
}
/**
* Sets the paymentFileLoadDao attribute value.
*
* @param paymentFileLoadDao The paymentFileLoadDao to set.
*/
public void setPaymentFileLoadDao(PaymentFileLoadDao paymentFileLoadDao) {
this.paymentFileLoadDao = paymentFileLoadDao;
}
/**
* Sets the parameterService attribute value.
*
* @param parameterService The parameterService to set.
*/
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
/**
* Sets the dateTimeService attribute value.
*
* @param dateTimeService The dateTimeService to set.
*/
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
/**
* Sets the accountService attribute value.
*
* @param accountService The accountService to set.
*/
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
/**
* Sets the subAccountService attribute value.
*
* @param subAccountService The subAccountService to set.
*/
public void setSubAccountService(SubAccountService subAccountService) {
this.subAccountService = subAccountService;
}
/**
* Sets the objectCodeService attribute value.
*
* @param objectCodeService The objectCodeService to set.
*/
public void setObjectCodeService(ObjectCodeService objectCodeService) {
this.objectCodeService = objectCodeService;
}
/**
* Sets the subObjectCodeService attribute value.
*
* @param subObjectCodeService The subObjectCodeService to set.
*/
public void setSubObjectCodeService(SubObjectCodeService subObjectCodeService) {
this.subObjectCodeService = subObjectCodeService;
}
/**
* Sets the kualiConfigurationService attribute value.
*
* @param kualiConfigurationService The kualiConfigurationService to set.
*/
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
/**
* Sets the bankService attribute value.
*
* @param bankService The bankService to set.
*/
public void setBankService(BankService bankService) {
this.bankService = bankService;
}
/**
* Sets the originationCodeService attribute value.
*
* @param originationCodeService The originationCodeService to set.
*/
public void setOriginationCodeService(OriginationCodeService originationCodeService) {
this.originationCodeService = originationCodeService;
}
/**
* Gets the businessObjectService attribute.
*
* @return Returns the businessObjectService.
*/
protected BusinessObjectService getBusinessObjectService() {
return businessObjectService;
}
/**
* Sets the businessObjectService attribute value.
*
* @param businessObjectService The businessObjectService to set.
*/
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
public void setDocumentTypeService(DocumentTypeService documentTypeService) {
this.documentTypeService = documentTypeService;
}
public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
this.dataDictionaryService = dataDictionaryService;
}
}