/*
* 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.purap.document.service.impl;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
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.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapConstants.ItemTypeCodes;
import org.kuali.kfs.module.purap.PurapConstants.PREQDocumentsStrings;
import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants.NRATaxParameters;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.PurapRuleConstants;
import org.kuali.kfs.module.purap.businessobject.AutoApproveExclude;
import org.kuali.kfs.module.purap.businessobject.ItemType;
import org.kuali.kfs.module.purap.businessobject.NegativePaymentRequestApprovalLimit;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestAccount;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.document.AccountsPayableDocument;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument;
import org.kuali.kfs.module.purap.document.dataaccess.PaymentRequestDao;
import org.kuali.kfs.module.purap.document.service.AccountsPayableService;
import org.kuali.kfs.module.purap.document.service.NegativePaymentRequestApprovalLimitService;
import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
import org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.document.validation.event.AttributedContinuePurapEvent;
import org.kuali.kfs.module.purap.document.validation.event.PurchasingAccountsPayableItemPreCalculateEvent;
import org.kuali.kfs.module.purap.exception.PurError;
import org.kuali.kfs.module.purap.service.PurapAccountingService;
import org.kuali.kfs.module.purap.service.PurapGeneralLedgerService;
import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry;
import org.kuali.kfs.module.purap.util.PurApItemUtils;
import org.kuali.kfs.module.purap.util.VendorGroupingHelper;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.FinancialSystemWorkflowHelperService;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.kfs.vnd.VendorConstants;
import org.kuali.kfs.vnd.businessobject.PaymentTermType;
import org.kuali.kfs.vnd.businessobject.VendorAddress;
import org.kuali.kfs.vnd.businessobject.VendorDetail;
import org.kuali.kfs.vnd.document.service.VendorService;
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.kew.api.WorkflowDocument;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.krad.bo.DocumentHeader;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.exception.InfrastructureException;
import org.kuali.rice.krad.exception.ValidationException;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.KualiRuleService;
import org.kuali.rice.krad.service.NoteService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADPropertyConstants;
import org.kuali.rice.krad.util.ObjectUtils;
import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
import org.springframework.transaction.annotation.Transactional;
/**
* This class provides services of use to a payment request document
*/
public class PaymentRequestServiceImpl implements PaymentRequestService {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentRequestServiceImpl.class);
protected DateTimeService dateTimeService;
protected DocumentService documentService;
protected NoteService noteService;
protected PurapService purapService;
protected PaymentRequestDao paymentRequestDao;
protected ParameterService parameterService;
protected ConfigurationService configurationService;
protected NegativePaymentRequestApprovalLimitService negativePaymentRequestApprovalLimitService;
protected PurapAccountingService purapAccountingService;
protected BusinessObjectService businessObjectService;
protected PurApWorkflowIntegrationService purapWorkflowIntegrationService;
protected WorkflowDocumentService workflowDocumentService;
protected AccountsPayableService accountsPayableService;
protected VendorService vendorService;
protected DataDictionaryService dataDictionaryService;
protected UniversityDateService universityDateService;
protected BankService bankService;
protected PurchaseOrderService purchaseOrderService;
protected FinancialSystemWorkflowHelperService financialSystemWorkflowHelperService;
protected KualiRuleService kualiRuleService;
protected PersonService personService;
/**
* NOTE: unused
*
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestsToExtractByCM(java.lang.String, org.kuali.kfs.module.purap.document.VendorCreditMemoDocument)
*/
@Override
@Deprecated
@NonTransactional
public Collection<PaymentRequestDocument> getPaymentRequestsToExtractByCM(String campusCode, VendorCreditMemoDocument cmd) {
LOG.debug("getPaymentRequestsByCM() started");
Date currentSqlDateMidnight = dateTimeService.getCurrentSqlDateMidnight();
List<PaymentRequestDocument> paymentRequestIterator = paymentRequestDao.getPaymentRequestsToExtract(campusCode, null, null, cmd.getVendorHeaderGeneratedIdentifier(), cmd.getVendorDetailAssignedIdentifier(), currentSqlDateMidnight);
return filterPaymentRequestByAppDocStatus(paymentRequestIterator,
PurapConstants.PaymentRequestStatuses.APPDOC_AUTO_APPROVED,
PurapConstants.PaymentRequestStatuses.APPDOC_DEPARTMENT_APPROVED);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestsToExtractByVendor(java.lang.String,
* org.kuali.kfs.module.purap.util.VendorGroupingHelper, java.sql.Date)
*/
@Override
@NonTransactional
public Collection<PaymentRequestDocument> getPaymentRequestsToExtractByVendor(String campusCode, VendorGroupingHelper vendor, Date onOrBeforePaymentRequestPayDate) {
LOG.debug("getPaymentRequestsByVendor() started");
Collection<PaymentRequestDocument> paymentRequestDocuments = paymentRequestDao.getPaymentRequestsToExtractForVendor(campusCode, vendor, onOrBeforePaymentRequestPayDate);
return filterPaymentRequestByAppDocStatus(paymentRequestDocuments,
PurapConstants.PaymentRequestStatuses.APPDOC_AUTO_APPROVED,
PurapConstants.PaymentRequestStatuses.APPDOC_DEPARTMENT_APPROVED);
}
/**
* @see org.kuali.module.purap.server.PaymentRequestService.getPaymentRequestsToExtract(Date)
*/
@Override
@NonTransactional
public Collection<PaymentRequestDocument> getPaymentRequestsToExtract(Date onOrBeforePaymentRequestPayDate) {
LOG.debug("getPaymentRequestsToExtract() started");
Collection<PaymentRequestDocument> paymentRequestIterator = paymentRequestDao.getPaymentRequestsToExtract(false, null, onOrBeforePaymentRequestPayDate);
return filterPaymentRequestByAppDocStatus(paymentRequestIterator,
PaymentRequestStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestsToExtractSpecialPayments(java.lang.String,
* java.sql.Date)
*/
@Override
@NonTransactional
public Collection<PaymentRequestDocument> getPaymentRequestsToExtractSpecialPayments(String chartCode, Date onOrBeforePaymentRequestPayDate) {
LOG.debug("getPaymentRequestsToExtractSpecialPayments() started");
Collection<PaymentRequestDocument> paymentRequestIterator = paymentRequestDao.getPaymentRequestsToExtract(true, chartCode, onOrBeforePaymentRequestPayDate);
return filterPaymentRequestByAppDocStatus(paymentRequestIterator,
PaymentRequestStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getImmediatePaymentRequestsToExtract(java.lang.String)
*/
@Override
@NonTransactional
public Collection<PaymentRequestDocument> getImmediatePaymentRequestsToExtract(String chartCode) {
LOG.debug("getImmediatePaymentRequestsToExtract() started");
Collection<PaymentRequestDocument> paymentRequestIterator = paymentRequestDao.getImmediatePaymentRequestsToExtract(chartCode);
return filterPaymentRequestByAppDocStatus(paymentRequestIterator,
PaymentRequestStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestToExtractByChart(java.lang.String,
* java.sql.Date)
*/
@Override
@NonTransactional
public Collection<PaymentRequestDocument> getPaymentRequestToExtractByChart(String chartCode, Date onOrBeforePaymentRequestPayDate) {
LOG.debug("getPaymentRequestToExtractByChart() started");
Collection<PaymentRequestDocument> paymentRequestIterator = paymentRequestDao.getPaymentRequestsToExtract(false, chartCode, onOrBeforePaymentRequestPayDate);
return filterPaymentRequestByAppDocStatus(paymentRequestIterator,
PaymentRequestStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService.autoApprovePaymentRequests()
*/
@Override
@NonTransactional
public boolean autoApprovePaymentRequests() {
if ( LOG.isInfoEnabled() ) {
LOG.info("Starting autoApprovePaymentRequests.");
}
boolean hadErrorAtLeastOneError = true;
// should objects from existing user session be copied over
Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight();
List<String> docNumbers = paymentRequestDao.getEligibleForAutoApproval(todayAtMidnight);
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- Initial filtering complete, returned " + new Integer(docNumbers.size()).toString() + " docs.");
}
String samt = parameterService.getParameterValueAsString(PaymentRequestDocument.class, PurapParameterConstants.PURAP_DEFAULT_NEGATIVE_PAYMENT_REQUEST_APPROVAL_LIMIT);
KualiDecimal defaultMinimumLimit = new KualiDecimal(samt);
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- Using default limit value of " + defaultMinimumLimit.toString() + ".");
}
List<PaymentRequestDocument> docs = new ArrayList<PaymentRequestDocument>();
for (String docNumber : docNumbers) {
PaymentRequestDocument paymentRequestDocument = getPaymentRequestByDocumentNumber(docNumber);
if (ObjectUtils.isNotNull(paymentRequestDocument)) {
hadErrorAtLeastOneError |= !autoApprovePaymentRequest(paymentRequestDocument, defaultMinimumLimit);
}
}
return hadErrorAtLeastOneError;
}
/**
* NOTE: in the event of auto-approval failure, this method may throw a RuntimeException, indicating to Spring transactional
* management that the transaction should be rolled back.
*
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#autoApprovePaymentRequest(java.lang.String,
* org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
@NonTransactional
public boolean autoApprovePaymentRequest(String docNumber, KualiDecimal defaultMinimumLimit) {
PaymentRequestDocument paymentRequestDocument = null;
try {
paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docNumber);
if (paymentRequestDocument.isHoldIndicator() || paymentRequestDocument.isPaymentRequestedCancelIndicator() || !Arrays.asList(PurapConstants.PaymentRequestStatuses.PREQ_STATUSES_FOR_AUTO_APPROVE).contains(paymentRequestDocument.getApplicationDocumentStatus())) {
// this condition is based on the conditions that PaymentRequestDaoOjb.getEligibleDocumentNumbersForAutoApproval()
// uses to query
// the database. Rechecking these conditions to ensure that the document is eligible for auto-approval, because
// we're not running things
// within the same transaction anymore and changes could have occurred since we called that method that make this
// document not auto-approvable
// note that this block does not catch all race conditions
// however, this error condition is not enough to make us return an error code, so just skip the document
LOG.warn("Payment Request Document " + paymentRequestDocument.getDocumentNumber() + " could not be auto-approved because it has either been placed on hold, " + " requested cancel, or does not have one of the PREQ statuses for auto-approve.");
return true;
}
if (autoApprovePaymentRequest(paymentRequestDocument, defaultMinimumLimit)) {
if ( LOG.isInfoEnabled() ) {
LOG.info("Auto-approval for payment request successful. Doc number: " + docNumber);
}
return true;
}
else {
LOG.error("Payment Request Document " + docNumber + " could not be auto-approved.");
return false;
}
}
catch (WorkflowException we) {
LOG.error("Exception encountered when retrieving document number " + docNumber + ".", we);
// throw a runtime exception up so that we can force a rollback
throw new RuntimeException("Exception encountered when retrieving document number " + docNumber + ".", we);
}
}
/**
* NOTE: in the event of auto-approval failure, this method may throw a RuntimeException, indicating to Spring transactional
* management that the transaction should be rolled back.
*
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#autoApprovePaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
@Transactional
public boolean autoApprovePaymentRequest(PaymentRequestDocument doc, KualiDecimal defaultMinimumLimit) {
if (isEligibleForAutoApproval(doc, defaultMinimumLimit)) {
try {
// Much of the rice frameworks assumes that document instances that are saved via DocumentService.saveDocument are
// those
// that were dynamically created by PojoFormBase (i.e., the Document instance wasn't created from OJB). We need to
// make
// a deep copy and materialize collections to fulfill that assumption so that collection elements will delete
// properly
// TODO: maybe rewriting PurapService.calculateItemTax could be rewritten so that the a deep copy doesn't need to be
// made
// by taking advantage of OJB's managed array lists
try {
ObjectUtils.materializeUpdateableCollections(doc);
for (PaymentRequestItem item : (List<PaymentRequestItem>) doc.getItems()) {
ObjectUtils.materializeUpdateableCollections(item);
}
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
doc = (PaymentRequestDocument) ObjectUtils.deepCopy(doc);
//set the auto approved indicator to true so that doRouteStatus method can use to
//change the app doc status.
doc.setAutoApprovedIndicator(true);
LOG.info("About to blanketApproveDocument, doc.getDocumentNumber()=" + doc.getDocumentNumber());
// su approve rather than blanket approve, so no ACK notifications would be generated by Rice
documentService.superUserApproveDocument(doc, "auto-approving: Total is below threshold.");
}
catch (WorkflowException we) {
LOG.error("Exception encountered when approving document number " + doc.getDocumentNumber() + ".", we);
// throw a runtime exception up so that we can force a rollback
throw new RuntimeException("Exception encountered when approving document number " + doc.getDocumentNumber() + ".", we);
}
}
return true;
}
/**
* Determines whether or not a payment request document can be automatically approved. FYI - If fiscal reviewers are allowed to
* save account changes without the full account validation running then this method must call full account validation to make
* sure auto approver is not blanket approving an invalid document according the the accounts on the items
*
* @param document The payment request document to be determined whether it can be automatically approved.
* @param defaultMinimumLimit The amount to be used as the minimum amount if no limit was found or the default is less than the
* limit.
* @return boolean true if the payment request document is eligible for auto approval.
*/
protected boolean isEligibleForAutoApproval(PaymentRequestDocument document, KualiDecimal defaultMinimumLimit) {
// Check if vendor is foreign.
if (document.getVendorDetail().getVendorHeader().getVendorForeignIndicator().booleanValue()) {
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] skipped due to a Foreign Vendor.");
}
return false;
}
// check to make sure the payment request isn't scheduled to stop in tax review.
if (purapWorkflowIntegrationService.willDocumentStopAtGivenFutureRouteNode(document, PaymentRequestStatuses.NODE_VENDOR_TAX_REVIEW)) {
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] skipped due to requiring Tax Review.");
}
return false;
}
// Change to not auto approve if positive approval required indicator set to Yes
if (document.isPaymentRequestPositiveApprovalIndicator()) {
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] skipped due to a Positive Approval Required Indicator set to Yes.");
}
return false;
}
// This minimum will be set to the minimum limit derived from all
// accounting lines on the document. If no limit is determined, the
// default will be used.
KualiDecimal minimumAmount = null;
// Iterate all source accounting lines on the document, deriving a
// minimum limit from each according to chart, chart and account, and
// chart and organization.
final List<SourceAccountingLine> summaryLines = purapAccountingService.generateSummary(document.getItems());
for (SourceAccountingLine line : summaryLines) {
// check to make sure the account is in the auto approve exclusion list
Map<String, Object> autoApproveMap = new HashMap<String, Object>();
autoApproveMap.put("chartOfAccountsCode", line.getChartOfAccountsCode());
autoApproveMap.put("accountNumber", line.getAccountNumber());
autoApproveMap.put("active", true);
AutoApproveExclude autoApproveExclude = businessObjectService.findByPrimaryKey(AutoApproveExclude.class, autoApproveMap);
if (autoApproveExclude != null) {
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] skipped due to source accounting line " + line.getSequenceNumber() +
" using Chart/Account [" + line.getChartOfAccountsCode() + "-" + line.getAccountNumber() +
"], which is excluded in the Auto Approve Exclusions table.");
}
return false;
}
minimumAmount = getMinimumLimitAmount(negativePaymentRequestApprovalLimitService.findByChart(line.getChartOfAccountsCode()), minimumAmount);
minimumAmount = getMinimumLimitAmount(negativePaymentRequestApprovalLimitService.findByChartAndAccount(line.getChartOfAccountsCode(), line.getAccountNumber()), minimumAmount);
minimumAmount = getMinimumLimitAmount(negativePaymentRequestApprovalLimitService.findByChartAndOrganization(line.getChartOfAccountsCode(), line.getOrganizationReferenceId()), minimumAmount);
}
// If Receiving required is set, it's not needed to check the negative payment request approval limit
if (document.isReceivingDocumentRequiredIndicator()) {
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] auto-approved (ignored dollar limit) due to Receiving Document Required Indicator set to Yes.");
}
return true;
}
// If no limit was found or the default is less than the limit, the default limit is used.
if (ObjectUtils.isNull(minimumAmount) || defaultMinimumLimit.compareTo(minimumAmount) < 0) {
minimumAmount = defaultMinimumLimit;
}
// The document is eligible for auto-approval if the document total is below the limit.
if (document.getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount().isLessThan(minimumAmount)) {
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] auto-approved due to document Total [" +
document.getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount() + "] being less than " +
(minimumAmount == defaultMinimumLimit ? "Default Auto-Approval Limit " : "Configured Auto-Approval Limit ") +
"of " + (minimumAmount == null ? "null" : minimumAmount.toString()) + ".");
}
return true;
}
if ( LOG.isInfoEnabled() ) {
LOG.info(" -- PayReq ["+document.getDocumentNumber()+"] skipped due to document Total [" +
document.getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount() + "] being greater than " +
(minimumAmount == defaultMinimumLimit ? "Default Auto-Approval Limit " : "Configured Auto-Approval Limit ") +
"of " + (minimumAmount == null ? "null" : minimumAmount.toString()) + ".");
}
return false;
}
/**
* This method iterates a collection of negative payment request approval limits and returns the minimum of a given minimum
* amount and the least among the limits in the collection.
*
* @param limits The collection of NegativePaymentRequestApprovalLimit to be used in determining the minimum limit amount.
* @param minimumAmount The amount to be compared with the collection of NegativePaymentRequestApprovalLimit to determine the
* minimum limit amount.
* @return The minimum of the given minimum amount and the least among the limits in the collection.
*/
protected KualiDecimal getMinimumLimitAmount(Collection<NegativePaymentRequestApprovalLimit> limits, KualiDecimal minimumAmount) {
for (NegativePaymentRequestApprovalLimit limit : limits) {
KualiDecimal amount = limit.getNegativePaymentRequestApprovalLimitAmount();
if (null == minimumAmount) {
minimumAmount = amount;
}
else if (minimumAmount.isGreaterThan(amount)) {
minimumAmount = amount;
}
}
return minimumAmount;
}
/**
* Retrieves a list of payment request documents with the given vendor id and invoice number.
*
* @param vendorHeaderGeneratedId The vendor header generated id.
* @param vendorDetailAssignedId The vendor detail assigned id.
* @param invoiceNumber The invoice number as entered by AP.
* @return List of payment request document.
*/
@Override
@NonTransactional
public List getPaymentRequestsByVendorNumber(Integer vendorHeaderGeneratedId, Integer vendorDetailAssignedId) {
LOG.debug("getActivePaymentRequestsByVendorNumber() started");
return paymentRequestDao.getActivePaymentRequestsByVendorNumber(vendorHeaderGeneratedId, vendorDetailAssignedId);
}
/**
* Retrieves a list of payment request documents with the given vendor id and invoice number.
*
* @param vendorHeaderGeneratedId The vendor header generated id.
* @param vendorDetailAssignedId The vendor detail assigned id.
* @param invoiceNumber The invoice number as entered by AP.
* @return List of payment request document.
*/
@Override
@NonTransactional
public List getPaymentRequestsByVendorNumberInvoiceNumber(Integer vendorHeaderGeneratedId, Integer vendorDetailAssignedId, String invoiceNumber) {
LOG.debug("getActivePaymentRequestsByVendorNumberInvoiceNumber() started");
return paymentRequestDao.getActivePaymentRequestsByVendorNumberInvoiceNumber(vendorHeaderGeneratedId, vendorDetailAssignedId, invoiceNumber);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#paymentRequestDuplicateMessages(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public HashMap<String, String> paymentRequestDuplicateMessages(PaymentRequestDocument document) {
HashMap<String, String> msgs;
msgs = new HashMap<String, String>();
Integer purchaseOrderId = document.getPurchaseOrderIdentifier();
if (ObjectUtils.isNotNull(document.getInvoiceDate())) {
if (purapService.isDateAYearBeforeToday(document.getInvoiceDate())) {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_INVOICE_DATE_A_YEAR_OR_MORE_PAST));
}
}
PurchaseOrderDocument po = document.getPurchaseOrderDocument();
if (po != null) {
Integer vendorDetailAssignedId = po.getVendorDetailAssignedIdentifier();
Integer vendorHeaderGeneratedId = po.getVendorHeaderGeneratedIdentifier();
List<PaymentRequestDocument> preqs = new ArrayList();
List<PaymentRequestDocument> preqsDuplicates = getPaymentRequestsByVendorNumber(vendorHeaderGeneratedId, vendorDetailAssignedId);
for (PaymentRequestDocument duplicatePREQ : preqsDuplicates) {
if (duplicatePREQ.getInvoiceNumber().toUpperCase().equals(document.getInvoiceNumber().toUpperCase())) {
// found the duplicate row... so add to the preqs list...
preqs.add(duplicatePREQ);
}
}
if (preqs.size() > 0) {
boolean addedMessage = false;
boolean foundCanceledPostApprove = false; // cancelled
boolean foundCanceledPreApprove = false; // voided
for (PaymentRequestDocument testPREQ : preqs) {
if (StringUtils.equals(testPREQ.getApplicationDocumentStatus(), PaymentRequestStatuses.APPDOC_CANCELLED_POST_AP_APPROVE)) {
foundCanceledPostApprove |= true;
}
else if (StringUtils.equals(testPREQ.getApplicationDocumentStatus(), PaymentRequestStatuses.APPDOC_CANCELLED_IN_PROCESS)) {
foundCanceledPreApprove |= true;
}
else {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE));
addedMessage = true;
break;
}
}
// Custom error message for duplicates related to cancelled/voided PREQs
if (!addedMessage) {
if (foundCanceledPostApprove && foundCanceledPreApprove) {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLEDORVOIDED));
}
else if (foundCanceledPreApprove) {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_VOIDED));
}
else if (foundCanceledPostApprove) {
// messages.add("errors.duplicate.vendor.invoice.cancelled");
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLED));
}
}
}
// Check that the invoice date and invoice total amount entered are not on any existing non-cancelled PREQs for this PO
preqs = getPaymentRequestsByPOIdInvoiceAmountInvoiceDate(purchaseOrderId, document.getVendorInvoiceAmount(), document.getInvoiceDate());
if (preqs.size() > 0) {
boolean addedMessage = false;
boolean foundCanceledPostApprove = false; // cancelled
boolean foundCanceledPreApprove = false; // voided
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT));
for (PaymentRequestDocument testPREQ : preqs) {
if (StringUtils.equalsIgnoreCase(testPREQ.getApplicationDocumentStatus(), PaymentRequestStatuses.APPDOC_CANCELLED_POST_AP_APPROVE)) {
foundCanceledPostApprove |= true;
}
else if (StringUtils.equalsIgnoreCase(testPREQ.getApplicationDocumentStatus(), PaymentRequestStatuses.APPDOC_CANCELLED_IN_PROCESS)) {
foundCanceledPreApprove |= true;
}
else {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT));
addedMessage = true;
break;
}
}
// Custom error message for duplicates related to cancelled/voided PREQs
if (!addedMessage) {
if (foundCanceledPostApprove && foundCanceledPreApprove) {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLEDORVOIDED));
}
else if (foundCanceledPreApprove) {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_VOIDED));
addedMessage = true;
}
else if (foundCanceledPostApprove) {
msgs.put(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLED));
addedMessage = true;
}
}
}
}
return msgs;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestByDocumentNumber(java.lang.String)
*/
@Override
@NonTransactional
public PaymentRequestDocument getPaymentRequestByDocumentNumber(String documentNumber) {
LOG.debug("getPaymentRequestByDocumentNumber() started");
if (ObjectUtils.isNotNull(documentNumber)) {
try {
PaymentRequestDocument doc = (PaymentRequestDocument) documentService.getByDocumentHeaderId(documentNumber);
return doc;
}
catch (WorkflowException e) {
String errorMessage = "Error getting payment request document from document service";
LOG.error("getPaymentRequestByDocumentNumber() " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
return null;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestById(java.lang.Integer)
*/
@Override
@NonTransactional
public PaymentRequestDocument getPaymentRequestById(Integer poDocId) {
return getPaymentRequestByDocumentNumber(paymentRequestDao.getDocumentNumberByPaymentRequestId(poDocId));
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestsByPurchaseOrderId(java.lang.Integer)
*/
@Override
@NonTransactional
public List<PaymentRequestDocument> getPaymentRequestsByPurchaseOrderId(Integer poDocId) {
List<PaymentRequestDocument> preqs = new ArrayList<PaymentRequestDocument>();
List<String> docNumbers = paymentRequestDao.getDocumentNumbersByPurchaseOrderId(poDocId);
for (String docNumber : docNumbers) {
PaymentRequestDocument preq = getPaymentRequestByDocumentNumber(docNumber);
if (ObjectUtils.isNotNull(preq)) {
preqs.add(preq);
}
}
return preqs;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestsByStatusAndPurchaseOrderId(java.lang.String, java.lang.Integer)
*/
@Override
@NonTransactional
public Map <String, String> getPaymentRequestsByStatusAndPurchaseOrderId(String applicationDocumentStatus, Integer purchaseOrderId) {
List<String> paymentRequestDocNumbers = paymentRequestDao.getDocumentNumbersByPurchaseOrderId(purchaseOrderId);
Map <String, String> paymentRequestResults = new HashMap<String, String>();
paymentRequestResults.put("hasInProcess", "N");
paymentRequestResults.put("checkInProcess", "N");
//if there are no payment request document numbers exist then there is no need to
//check for application document status on the workflow documents....
if (paymentRequestDocNumbers == null || paymentRequestDocNumbers.isEmpty()) {
return paymentRequestResults;
}
//helper method to filter the workflow documents that are created for Preq documents.
//updates the map for hasInProcess value to Y if records found for app doc status
//else sets the value of checkInProcess = Y.
filterPaymentRequestByAppDocStatus(paymentRequestResults, paymentRequestDocNumbers, applicationDocumentStatus);
return paymentRequestResults;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestsByPOIdInvoiceAmountInvoiceDate(java.lang.Integer,
* org.kuali.rice.core.api.util.type.KualiDecimal, java.sql.Date)
*/
@Override
@NonTransactional
public List<PaymentRequestDocument> getPaymentRequestsByPOIdInvoiceAmountInvoiceDate(Integer poId, KualiDecimal invoiceAmount, Date invoiceDate) {
LOG.debug("getPaymentRequestsByPOIdInvoiceAmountInvoiceDate() started");
return paymentRequestDao.getActivePaymentRequestsByPOIdInvoiceAmountInvoiceDate(poId, invoiceAmount, invoiceDate);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#isInvoiceDateAfterToday(java.sql.Date)
*/
@Override
@NonTransactional
public boolean isInvoiceDateAfterToday(Date invoiceDate) {
// Check invoice date to make sure it is today or before
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR, 11);
now.set(Calendar.MINUTE, 59);
now.set(Calendar.SECOND, 59);
now.set(Calendar.MILLISECOND, 59);
Timestamp nowTime = new Timestamp(now.getTimeInMillis());
Calendar invoiceDateC = Calendar.getInstance();
invoiceDateC.setTime(invoiceDate);
// set time to midnight
invoiceDateC.set(Calendar.HOUR, 0);
invoiceDateC.set(Calendar.MINUTE, 0);
invoiceDateC.set(Calendar.SECOND, 0);
invoiceDateC.set(Calendar.MILLISECOND, 0);
Timestamp invoiceDateTime = new Timestamp(invoiceDateC.getTimeInMillis());
return ((invoiceDateTime.compareTo(nowTime)) > 0);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#calculatePayDate(java.sql.Date,
* org.kuali.kfs.vnd.businessobject.PaymentTermType)
*/
@Override
@NonTransactional
public java.sql.Date calculatePayDate(Date invoiceDate, PaymentTermType terms) {
LOG.debug("calculatePayDate() started");
// calculate the invoice + processed calendar
Calendar invoicedDateCalendar = dateTimeService.getCalendar(invoiceDate);
Calendar processedDateCalendar = dateTimeService.getCurrentCalendar();
// add default number of days to processed
String defaultDays = parameterService.getParameterValueAsString(PaymentRequestDocument.class, PurapParameterConstants.PURAP_PREQ_PAY_DATE_DEFAULT_NUMBER_OF_DAYS);
processedDateCalendar.add(Calendar.DAY_OF_MONTH, Integer.parseInt(defaultDays));
if (ObjectUtils.isNull(terms) || StringUtils.isEmpty(terms.getVendorPaymentTermsCode())) {
invoicedDateCalendar.add(Calendar.DAY_OF_MONTH, PurapConstants.PREQ_PAY_DATE_EMPTY_TERMS_DEFAULT_DAYS);
return returnLaterDate(invoicedDateCalendar, processedDateCalendar);
}
// Retrieve pay date variation parameter (currently defined as 2). See parameter description for explanation of it's use.
String payDateVariance = parameterService.getParameterValueAsString(PaymentRequestDocument.class, PurapParameterConstants.PURAP_PREQ_PAY_DATE_VARIANCE);
Integer payDateVarianceInt = Integer.valueOf(payDateVariance);
Integer discountDueNumber = terms.getVendorDiscountDueNumber();
Integer netDueNumber = terms.getVendorNetDueNumber();
if (ObjectUtils.isNotNull(discountDueNumber)) {
// Decrease discount due number by the pay date variance
discountDueNumber -= payDateVarianceInt;
if (discountDueNumber < 0) {
discountDueNumber = 0;
}
String discountDueTypeDescription = terms.getVendorDiscountDueTypeDescription();
paymentTermsDateCalculation(discountDueTypeDescription, invoicedDateCalendar, discountDueNumber);
}
else if (ObjectUtils.isNotNull(netDueNumber)) {
// Decrease net due number by the pay date variance
netDueNumber -= payDateVarianceInt;
if (netDueNumber < 0) {
netDueNumber = 0;
}
String netDueTypeDescription = terms.getVendorNetDueTypeDescription();
paymentTermsDateCalculation(netDueTypeDescription, invoicedDateCalendar, netDueNumber);
}
else {
throw new RuntimeException("Neither discount or net number were specified for this payment terms type");
}
// return the later date
return returnLaterDate(invoicedDateCalendar, processedDateCalendar);
}
/**
* Returns whichever date is later, the invoicedDateCalendar or the processedDateCalendar.
*
* @param invoicedDateCalendar One of the dates to be used in determining which date is later.
* @param processedDateCalendar The other date to be used in determining which date is later.
* @return The date which is the later of the two given dates in the input parameters.
*/
protected java.sql.Date returnLaterDate(Calendar invoicedDateCalendar, Calendar processedDateCalendar) {
if (invoicedDateCalendar.after(processedDateCalendar)) {
return new java.sql.Date(invoicedDateCalendar.getTimeInMillis());
}
else {
return new java.sql.Date(processedDateCalendar.getTimeInMillis());
}
}
/**
* Calculates the paymentTermsDate given the dueTypeDescription, invoicedDateCalendar and the dueNumber.
*
* @param dueTypeDescription The due type description of the payment term.
* @param invoicedDateCalendar The Calendar object of the invoice date.
* @param discountDueNumber Either the vendorDiscountDueNumber or the vendorDiscountDueNumber of the payment term.
*/
protected void paymentTermsDateCalculation(String dueTypeDescription, Calendar invoicedDateCalendar, Integer dueNumber) {
if (StringUtils.equals(dueTypeDescription, PurapConstants.PREQ_PAY_DATE_DATE)) {
// date specified set to date in next month
invoicedDateCalendar.add(Calendar.MONTH, 1);
invoicedDateCalendar.set(Calendar.DAY_OF_MONTH, dueNumber.intValue());
}
else if (StringUtils.equals(PurapConstants.PREQ_PAY_DATE_DAYS, dueTypeDescription)) {
// days specified go forward that number
invoicedDateCalendar.add(Calendar.DAY_OF_MONTH, dueNumber.intValue());
}
else {
// improper string
throw new RuntimeException("missing payment terms description or not properly enterred on payment term maintenance doc");
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#calculatePaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* boolean)
*/
@Override
@NonTransactional
public void calculatePaymentRequest(PaymentRequestDocument paymentRequest, boolean updateDiscount) {
LOG.debug("calculatePaymentRequest() started");
// general calculation, i.e. for the whole preq document
if (ObjectUtils.isNull(paymentRequest.getPaymentRequestPayDate())) {
paymentRequest.setPaymentRequestPayDate(calculatePayDate(paymentRequest.getInvoiceDate(), paymentRequest.getVendorPaymentTerms()));
}
distributeAccounting(paymentRequest);
purapService.calculateTax(paymentRequest);
// do proration for full order and trade in
purapService.prorateForTradeInAndFullOrderDiscount(paymentRequest);
// do proration for payment terms discount
if (updateDiscount) {
calculateDiscount(paymentRequest);
}
distributeAccounting(paymentRequest);
}
/**
* Calculates the discount item for this paymentRequest.
*
* @param paymentRequestDocument The payment request document whose discount to be calculated.
*/
protected void calculateDiscount(PaymentRequestDocument paymentRequestDocument) {
PaymentRequestItem discountItem = findDiscountItem(paymentRequestDocument);
// find out if we really need the discount item
PaymentTermType pt = paymentRequestDocument.getVendorPaymentTerms();
if ((pt != null) && (pt.getVendorPaymentTermsPercent() != null) && (BigDecimal.ZERO.compareTo(pt.getVendorPaymentTermsPercent()) != 0)) {
if (discountItem == null) {
// set discountItem and add to items
// this is probably not the best way of doing it but should work for now if we start excluding discount from below
// we will need to manually add
purapService.addBelowLineItems(paymentRequestDocument);
// fix up below the line items
removeIneligibleAdditionalCharges(paymentRequestDocument);
discountItem = findDiscountItem(paymentRequestDocument);
}
// Deleted the discountItem.getExtendedPrice() null and isZero
PaymentRequestItem fullOrderItem = findFullOrderDiscountItem(paymentRequestDocument);
KualiDecimal fullOrderAmount = KualiDecimal.ZERO;
KualiDecimal fullOrderTaxAmount = KualiDecimal.ZERO;
if (fullOrderItem != null) {
fullOrderAmount = (ObjectUtils.isNotNull(fullOrderItem.getExtendedPrice())) ? fullOrderItem.getExtendedPrice() : KualiDecimal.ZERO;
fullOrderTaxAmount = (ObjectUtils.isNotNull(fullOrderItem.getItemTaxAmount())) ? fullOrderItem.getItemTaxAmount() : KualiDecimal.ZERO;
}
KualiDecimal totalCost = paymentRequestDocument.getTotalPreTaxDollarAmountAboveLineItems().add(fullOrderAmount);
PurApItem tradeInItem = paymentRequestDocument.getTradeInItem();
if (ObjectUtils.isNotNull(tradeInItem)) {
totalCost = totalCost.add(tradeInItem.getTotalAmount());
}
BigDecimal discountAmount = pt.getVendorPaymentTermsPercent().multiply(totalCost.bigDecimalValue()).multiply(new BigDecimal(PurapConstants.PREQ_DISCOUNT_MULT));
// do we really need to set both, not positive, but probably won't hurt
discountItem.setItemUnitPrice(discountAmount.setScale(2, KualiDecimal.ROUND_BEHAVIOR));
discountItem.setExtendedPrice(new KualiDecimal(discountAmount));
// set tax amount
boolean salesTaxInd = parameterService.getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
boolean useTaxIndicator = paymentRequestDocument.isUseTaxIndicator();
if (salesTaxInd == true && useTaxIndicator == false) {
KualiDecimal totalTax = paymentRequestDocument.getTotalTaxAmountAboveLineItems().add(fullOrderTaxAmount);
BigDecimal discountTaxAmount = null;
if (totalCost.isNonZero()) {
discountTaxAmount = discountAmount.divide(totalCost.bigDecimalValue()).multiply(totalTax.bigDecimalValue());
}
else {
discountTaxAmount = BigDecimal.ZERO;
}
discountItem.setItemTaxAmount(new KualiDecimal(discountTaxAmount.setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
}
// set document
discountItem.setPurapDocument(paymentRequestDocument);
}
else { // no discount
if (discountItem != null) {
paymentRequestDocument.getItems().remove(discountItem);
}
}
}
@Override
@NonTransactional
public void clearTax(PaymentRequestDocument document) {
// remove all existing tax items added by previous calculation
removeTaxItems(document);
// reset values
document.setTaxClassificationCode(null);
document.setTaxFederalPercent(null);
document.setTaxStatePercent(null);
document.setTaxCountryCode(null);
document.setTaxNQIId(null);
document.setTaxForeignSourceIndicator(false);
document.setTaxExemptTreatyIndicator(false);
document.setTaxOtherExemptIndicator(false);
document.setTaxGrossUpIndicator(false);
document.setTaxUSAIDPerDiemIndicator(false);
document.setTaxSpecialW4Amount(null);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#calculateTaxArea(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public void calculateTaxArea(PaymentRequestDocument preq) {
LOG.debug("calculateTaxArea() started");
// remove all existing tax items added by previous calculation
removeTaxItems(preq);
// don't need to calculate tax items if TaxClassificationCode is N (Non_Reportable)
if (StringUtils.equalsIgnoreCase(preq.getTaxClassificationCode(), "N")) {
return;
}
// reserve the grand total excluding any tax amount, to be used as the base to compute all tax items
// if we don't reserve this, the pre-tax total could be changed as new tax items are added
BigDecimal taxableAmount = preq.getGrandPreTaxTotal().bigDecimalValue();
// generate and add state tax gross up item and its accounting line, update total amount,
// if gross up indicator is true and tax rate is non-zero
if (preq.getTaxGrossUpIndicator() && preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
PurApItem stateGrossItem = addTaxItem(preq, ItemTypeCodes.ITEM_TYPE_STATE_GROSS_CODE, taxableAmount);
}
// generate and add state tax item and its accounting line, update total amount, if tax rate is non-zero
if (preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
PurApItem stateTaxItem = addTaxItem(preq, ItemTypeCodes.ITEM_TYPE_STATE_TAX_CODE, taxableAmount);
}
// generate and add federal tax gross up item and its accounting line, update total amount,
// if gross up indicator is true and tax rate is non-zero
if (preq.getTaxGrossUpIndicator() && preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
PurApItem federalGrossItem = addTaxItem(preq, ItemTypeCodes.ITEM_TYPE_FEDERAL_GROSS_CODE, taxableAmount);
}
// generate and add federal tax item and its accounting line, update total amount, if tax rate is non-zero
if (preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
PurApItem federalTaxItem = addTaxItem(preq, ItemTypeCodes.ITEM_TYPE_FEDERAL_TAX_CODE, taxableAmount);
}
// FIXME if user request to add zero tax lines and remove them after tax approval,
// then remove the conditions above when adding the tax lines, and
// add a branch in PaymentRequestDocument.processNodeChange to call PurapService.deleteUnenteredItems
}
/**
* Removes all existing NRA tax items from the specified payment request.
*
* @param preq The payment request from which all tax items are to be removed.
*/
protected void removeTaxItems(PaymentRequestDocument preq) {
List<PurApItem> items = preq.getItems();
for (int i = 0; i < items.size(); i++) {
PurApItem item = items.get(i);
String code = item.getItemTypeCode();
if (ItemTypeCodes.ITEM_TYPE_FEDERAL_TAX_CODE.equals(code) || ItemTypeCodes.ITEM_TYPE_STATE_TAX_CODE.equals(code) || ItemTypeCodes.ITEM_TYPE_FEDERAL_GROSS_CODE.equals(code) || ItemTypeCodes.ITEM_TYPE_STATE_GROSS_CODE.equals(code)) {
items.remove(i--);
}
}
}
/**
* Generates a NRA tax item and adds to the specified payment request, according to the specified item type code.
*
* @param preq The payment request the tax item will be added to.
* @param itemTypeCode The item type code for the tax item.
* @param taxableAmount The amount to which tax is computed against.
* @return A fully populated PurApItem instance representing NRA tax amount data for the specified payment request.
*/
protected PurApItem addTaxItem(PaymentRequestDocument preq, String itemTypeCode, BigDecimal taxableAmount) {
PurApItem taxItem = null;
try {
taxItem = (PurApItem) preq.getItemClass().newInstance();
}
catch (IllegalAccessException e) {
throw new InfrastructureException("Unable to access itemClass", e);
}
catch (InstantiationException e) {
throw new InfrastructureException("Unable to instantiate itemClass", e);
}
// add item to preq before adding the accounting line
taxItem.setItemTypeCode(itemTypeCode);
preq.addItem(taxItem);
// generate and add tax accounting line
PurApAccountingLine taxLine = addTaxAccountingLine(taxItem, taxableAmount);
// set extended price amount as now it's calculated when accounting line is generated
taxItem.setItemUnitPrice(taxLine.getAmount().bigDecimalValue());
taxItem.setExtendedPrice(taxLine.getAmount());
// use item type description as the item description
ItemType itemType = new ItemType();
itemType.setItemTypeCode(itemTypeCode);
itemType = (ItemType) businessObjectService.retrieve(itemType);
taxItem.setItemType(itemType);
taxItem.setItemDescription(itemType.getItemTypeDescription());
return taxItem;
}
/**
* Generates a PurAP accounting line and adds to the specified tax item.
*
* @param taxItem The specified tax item the accounting line will be associated with.
* @param taxableAmount The amount to which tax is computed against.
* @return A fully populated PurApAccountingLine instance for the specified tax item.
*/
protected PurApAccountingLine addTaxAccountingLine(PurApItem taxItem, BigDecimal taxableAmount) {
PaymentRequestDocument preq = taxItem.getPurapDocument();
PurApAccountingLine taxLine = null;
try {
taxLine = (PurApAccountingLine) taxItem.getAccountingLineClass().newInstance();
}
catch (IllegalAccessException e) {
throw new InfrastructureException("Unable to access sourceAccountingLineClass", e);
}
catch (InstantiationException e) {
throw new InfrastructureException("Unable to instantiate sourceAccountingLineClass", e);
}
// tax item type indicators
boolean isFederalTax = ItemTypeCodes.ITEM_TYPE_FEDERAL_TAX_CODE.equals(taxItem.getItemTypeCode());
boolean isFederalGross = ItemTypeCodes.ITEM_TYPE_FEDERAL_GROSS_CODE.equals(taxItem.getItemTypeCode());
boolean isStateTax = ItemTypeCodes.ITEM_TYPE_STATE_TAX_CODE.equals(taxItem.getItemTypeCode());
boolean isStateGross = ItemTypeCodes.ITEM_TYPE_STATE_GROSS_CODE.equals(taxItem.getItemTypeCode());
boolean isFederal = isFederalTax || isFederalGross; // true for federal tax/gross; false for state tax/gross
boolean isGross = isFederalGross || isStateGross; // true for federal/state gross, false for federal/state tax
// obtain accounting line info according to tax item type code
String taxChart = null;
String taxAccount = null;
String taxObjectCode = null;
if (isGross) {
// for gross up tax items, use preq's first item's first accounting line, which shall exist at this point
AccountingLine line1 = preq.getFirstAccount();
taxChart = line1.getChartOfAccountsCode();
taxAccount = line1.getAccountNumber();
taxObjectCode = line1.getFinancialObjectCode();
}
else if (isFederalTax) {
// for federal tax item, get chart, account, object code info from parameters
taxChart = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX);
taxAccount = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX);
taxObjectCode = parameterService.getSubParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, preq.getTaxClassificationCode());
if (StringUtils.isBlank(taxChart) || StringUtils.isBlank(taxAccount) || StringUtils.isBlank(taxObjectCode)) {
LOG.error("Unable to retrieve federal tax parameters.");
throw new RuntimeException("Unable to retrieve federal tax parameters.");
}
}
else if (isStateTax) {
// for state tax item, get chart, account, object code info from parameters
taxChart = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX);
taxAccount = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX);
taxObjectCode = parameterService.getSubParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, preq.getTaxClassificationCode());
if (StringUtils.isBlank(taxChart) || StringUtils.isBlank(taxAccount) || StringUtils.isBlank(taxObjectCode)) {
LOG.error("Unable to retrieve state tax parameters.");
throw new RuntimeException("Unable to retrieve state tax parameters.");
}
}
// calculate tax amount according to gross up indicator and federal/state tax type
/*
* The formula of tax and gross up amount are as follows: if (not gross up) gross not existing taxFederal/State = - amount *
* rateFederal/State otherwise gross up grossFederal/State = amount * rateFederal/State / (1 - rateFederal - rateState) tax
* = - gross
*/
// pick federal/state tax rate
BigDecimal taxPercentFederal = preq.getTaxFederalPercent();
BigDecimal taxPercentState = preq.getTaxStatePercent();
BigDecimal taxPercent = isFederal ? taxPercentFederal : taxPercentState;
// divider value according to gross up or not
BigDecimal taxDivider = new BigDecimal(100);
if (preq.getTaxGrossUpIndicator()) {
taxDivider = taxDivider.subtract(taxPercentFederal.add(taxPercentState));
}
// tax = amount * rate / divider
BigDecimal taxAmount = taxableAmount.multiply(taxPercent);
taxAmount = taxAmount.divide(taxDivider, 5, BigDecimal.ROUND_HALF_UP);
// tax is always negative, since it reduces the total amount; while gross up is always the positive of tax
if (!isGross) {
taxAmount = taxAmount.negate();
}
// populate necessary accounting line fields
taxLine.setDocumentNumber(preq.getDocumentNumber());
taxLine.setSequenceNumber(preq.getNextSourceLineNumber());
taxLine.setChartOfAccountsCode(taxChart);
taxLine.setAccountNumber(taxAccount);
taxLine.setFinancialObjectCode(taxObjectCode);
taxLine.setAmount(new KualiDecimal(taxAmount));
// add the accounting line to the item
taxLine.setItemIdentifier(taxItem.getItemIdentifier());
taxLine.setPurapItem(taxItem);
taxItem.getSourceAccountingLines().add(taxLine);
return taxLine;
}
/**
* Finds the discount item of the payment request document.
*
* @param paymentRequestDocument The payment request document to be used to find the discount item.
* @return The discount item if it exists.
*/
protected PaymentRequestItem findDiscountItem(PaymentRequestDocument paymentRequestDocument) {
PaymentRequestItem discountItem = null;
for (PaymentRequestItem preqItem : (List<PaymentRequestItem>) paymentRequestDocument.getItems()) {
if (StringUtils.equals(preqItem.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) {
discountItem = preqItem;
break;
}
}
return discountItem;
}
/**
* Finds the full order discount item of the payment request document.
*
* @param paymentRequestDocument The payment request document to be used to find the full order discount item.
* @return The discount item if it exists.
*/
protected PaymentRequestItem findFullOrderDiscountItem(PaymentRequestDocument paymentRequestDocument) {
PaymentRequestItem discountItem = null;
for (PaymentRequestItem preqItem : (List<PaymentRequestItem>) paymentRequestDocument.getItems()) {
if (StringUtils.equals(preqItem.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) {
discountItem = preqItem;
break;
}
}
return discountItem;
}
/**
* Distributes accounts for a payment request document.
*
* @param paymentRequestDocument
*/
protected void distributeAccounting(PaymentRequestDocument paymentRequestDocument) {
// update the account amounts before doing any distribution
purapAccountingService.updateAccountAmounts(paymentRequestDocument);
String accountDistributionMethod = paymentRequestDocument.getAccountDistributionMethod();
for (PaymentRequestItem item : (List<PaymentRequestItem>) paymentRequestDocument.getItems()) {
KualiDecimal totalAmount = KualiDecimal.ZERO;
List<PurApAccountingLine> distributedAccounts = null;
List<SourceAccountingLine> summaryAccounts = null;
Set excludedItemTypeCodes = new HashSet();
excludedItemTypeCodes.add(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE);
// skip above the line
if (item.getItemType().isLineItemIndicator()) {
continue;
}
if ((item.getSourceAccountingLines().isEmpty()) && (ObjectUtils.isNotNull(item.getExtendedPrice())) && (KualiDecimal.ZERO.compareTo(item.getExtendedPrice()) != 0)) {
if ((StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE, item.getItemType().getItemTypeCode())) && (paymentRequestDocument.getGrandTotal() != null) && ((KualiDecimal.ZERO.compareTo(paymentRequestDocument.getGrandTotal()) != 0))) {
// No discount is applied to other item types other than item line
// See KFSMI-5210 for details
// total amount should be the line item total, not the grand total
totalAmount = paymentRequestDocument.getLineItemTotal();
// prorate item line accounts only
Set includedItemTypeCodes = new HashSet();
includedItemTypeCodes.add(PurapConstants.ItemTypeCodes.ITEM_TYPE_ITEM_CODE);
includedItemTypeCodes.add(PurapConstants.ItemTypeCodes.ITEM_TYPE_SERVICE_CODE);
summaryAccounts = purapAccountingService.generateSummaryIncludeItemTypesAndNoZeroTotals(paymentRequestDocument.getItems(), includedItemTypeCodes);
//if summaryAccount is empty then do not call generateAccountDistributionForProration as
//there is a check in that method to throw NPE if accounts percents == 0..
//KFSMI-8487
if (summaryAccounts != null) {
distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, PurapConstants.PRORATION_SCALE, PaymentRequestAccount.class);
}
if (PurapConstants.AccountDistributionMethodCodes.SEQUENTIAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
purapAccountingService.updatePreqAccountAmountsWithTotal(distributedAccounts, item.getTotalAmount());
} else {
boolean rulePassed = true;
// check any business rules
rulePassed &= kualiRuleService.applyRules(new PurchasingAccountsPayableItemPreCalculateEvent(paymentRequestDocument, item));
if (rulePassed) {
purapAccountingService.updatePreqProporationalAccountAmountsWithTotal(distributedAccounts, item.getTotalAmount());
}
}
}
else {
PurchaseOrderItem poi = item.getPurchaseOrderItem();
if ((poi != null) && (poi.getSourceAccountingLines() != null) && (!(poi.getSourceAccountingLines().isEmpty())) && (poi.getExtendedPrice() != null) && ((KualiDecimal.ZERO.compareTo(poi.getExtendedPrice())) != 0)) {
// use accounts from purchase order item matching this item
// account list of current item is already empty
item.generateAccountListFromPoItemAccounts(poi.getSourceAccountingLines());
}
else {
totalAmount = paymentRequestDocument.getPurchaseOrderDocument().getTotalDollarAmountAboveLineItems();
purapAccountingService.updateAccountAmounts(paymentRequestDocument.getPurchaseOrderDocument());
summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils.getAboveTheLineOnly(paymentRequestDocument.getPurchaseOrderDocument().getItems()));
//if summaryAccount is empty then do not call generateAccountDistributionForProration as
//there is a check in that method to throw NPE if accounts percents == 0..
//KFSMI-8487
if (summaryAccounts != null) {
distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, new Integer("6"), PaymentRequestAccount.class);
}
}
}
if (CollectionUtils.isNotEmpty(distributedAccounts) && CollectionUtils.isEmpty(item.getSourceAccountingLines())) {
item.setSourceAccountingLines(distributedAccounts);
}
}
}
// update again now that distribute is finished. (Note: we may not need this anymore now that I added updateItem line above
//leave the call below since we need to this when sequential method is used on the document.
purapAccountingService.updateAccountAmounts(paymentRequestDocument);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#addHoldOnPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* java.lang.String)
*/
@Override
@NonTransactional
public PaymentRequestDocument addHoldOnPaymentRequest(PaymentRequestDocument document, String note) throws Exception {
// save the note
Note noteObj = documentService.createNoteFromDocument(document, note);
document.addNote(noteObj);
noteService.save(noteObj);
document.setHoldIndicator(true);
document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
purapService.saveDocumentNoValidation(document);
return document;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#removeHoldOnPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public PaymentRequestDocument removeHoldOnPaymentRequest(PaymentRequestDocument document, String note) throws Exception {
// save the note
Note noteObj = documentService.createNoteFromDocument(document, note);
document.addNote(noteObj);
noteService.save(noteObj);
document.setHoldIndicator(false);
document.setLastActionPerformedByPersonId(null);
purapService.saveDocumentNoValidation(document);
return document;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#addHoldOnPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* java.lang.String)
*/
@Override
@NonTransactional
public void requestCancelOnPaymentRequest(PaymentRequestDocument document, String note) throws Exception {
// save the note
Note noteObj = documentService.createNoteFromDocument(document, note);
document.addNote(noteObj);
noteService.save(noteObj);
document.setPaymentRequestedCancelIndicator(true);
document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
document.setAccountsPayableRequestCancelIdentifier(GlobalVariables.getUserSession().getPerson().getPrincipalId());
purapService.saveDocumentNoValidation(document);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#removeHoldOnPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public void removeRequestCancelOnPaymentRequest(PaymentRequestDocument document, String note) throws Exception {
// save the note
Note noteObj = documentService.createNoteFromDocument(document, note);
document.addNote(noteObj);
noteService.save(noteObj);
clearRequestCancelFields(document);
purapService.saveDocumentNoValidation(document);
}
/**
* Clears the request cancel fields.
*
* @param document The payment request document whose request cancel fields to be cleared.
*/
protected void clearRequestCancelFields(PaymentRequestDocument document) {
document.setPaymentRequestedCancelIndicator(false);
document.setLastActionPerformedByPersonId(null);
document.setAccountsPayableRequestCancelIdentifier(null);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#isExtracted(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public boolean isExtracted(PaymentRequestDocument document) {
return (ObjectUtils.isNull(document.getExtractedTimestamp()) ? false : true);
}
protected boolean isBeingAdHocRouted(PaymentRequestDocument document) {
return financialSystemWorkflowHelperService.isAdhocApprovalRequestedForPrincipal(document.getDocumentHeader().getWorkflowDocument(), GlobalVariables.getUserSession().getPrincipalId());
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#cancelExtractedPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* java.lang.String)
*/
@Override
@NonTransactional
public void cancelExtractedPaymentRequest(PaymentRequestDocument paymentRequest, String note) {
LOG.debug("cancelExtractedPaymentRequest() started");
if (PaymentRequestStatuses.CANCELLED_STATUSES.contains(paymentRequest.getApplicationDocumentStatus())) {
LOG.debug("cancelExtractedPaymentRequest() ended");
return;
}
try {
Note cancelNote = documentService.createNoteFromDocument(paymentRequest, note);
paymentRequest.addNote(cancelNote);
noteService.save(cancelNote);
}
catch (Exception e) {
throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE, e);
}
// cancel extracted should not reopen PO
paymentRequest.setReopenPurchaseOrderIndicator(false);
getAccountsPayableService().cancelAccountsPayableDocument(paymentRequest, ""); // Performs save, so
// no explicit save
// is necessary
if (LOG.isDebugEnabled()) {
LOG.debug("cancelExtractedPaymentRequest() PREQ " + paymentRequest.getPurapDocumentIdentifier() + " Cancelled Without Workflow");
LOG.debug("cancelExtractedPaymentRequest() ended");
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#resetExtractedPaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* java.lang.String)
*/
@Override
@NonTransactional
public void resetExtractedPaymentRequest(PaymentRequestDocument paymentRequest, String note) {
LOG.debug("resetExtractedPaymentRequest() started");
if (PaymentRequestStatuses.CANCELLED_STATUSES.contains(paymentRequest.getApplicationDocumentStatus())) {
LOG.debug("resetExtractedPaymentRequest() ended");
return;
}
paymentRequest.setExtractedTimestamp(null);
paymentRequest.setPaymentPaidTimestamp(null);
String noteText = "This Payment Request is being reset for extraction by PDP " + note;
try {
Note resetNote = documentService.createNoteFromDocument(paymentRequest, noteText);
paymentRequest.addNote(resetNote);
noteService.save(resetNote);
}
catch (Exception e) {
throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
}
purapService.saveDocumentNoValidation(paymentRequest);
if (LOG.isDebugEnabled()) {
LOG.debug("resetExtractedPaymentRequest() PREQ " + paymentRequest.getPurapDocumentIdentifier() + " Reset from Extracted status");
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#populatePaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public void populatePaymentRequest(PaymentRequestDocument paymentRequestDocument) {
PurchaseOrderDocument purchaseOrderDocument = paymentRequestDocument.getPurchaseOrderDocument();
// make a call to search for expired/closed accounts
HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = getAccountsPayableService().getExpiredOrClosedAccountList(paymentRequestDocument);
paymentRequestDocument.populatePaymentRequestFromPurchaseOrder(purchaseOrderDocument, expiredOrClosedAccountList);
paymentRequestDocument.getDocumentHeader().setDocumentDescription(createPreqDocumentDescription(paymentRequestDocument.getPurchaseOrderIdentifier(), paymentRequestDocument.getVendorName()));
// write a note for expired/closed accounts if any exist and add a message stating there were expired/closed accounts at the
// top of the document
getAccountsPayableService().generateExpiredOrClosedAccountNote(paymentRequestDocument, expiredOrClosedAccountList);
// set indicator so a message is displayed for accounts that were replaced due to expired/closed status
if (!expiredOrClosedAccountList.isEmpty()) {
paymentRequestDocument.setContinuationAccountIndicator(true);
}
// add discount item
calculateDiscount(paymentRequestDocument);
// distribute accounts (i.e. proration)
distributeAccounting(paymentRequestDocument);
// set bank code to default bank code in the system parameter
Bank defaultBank = bankService.getDefaultBankByDocType(paymentRequestDocument.getClass());
if (defaultBank != null) {
paymentRequestDocument.setBankCode(defaultBank.getBankCode());
paymentRequestDocument.setBank(defaultBank);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#createPreqDocumentDescription(java.lang.Integer,
* java.lang.String)
*/
@Override
@NonTransactional
public String createPreqDocumentDescription(Integer purchaseOrderIdentifier, String vendorName) {
StringBuffer descr = new StringBuffer("");
descr.append("PO: ");
descr.append(purchaseOrderIdentifier);
descr.append(" Vendor: ");
descr.append(StringUtils.trimToEmpty(vendorName));
int noteTextMaxLength = dataDictionaryService.getAttributeMaxLength(DocumentHeader.class, KRADPropertyConstants.DOCUMENT_DESCRIPTION).intValue();
if (noteTextMaxLength >= descr.length()) {
return descr.toString();
}
else {
return descr.toString().substring(0, noteTextMaxLength);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#populateAndSavePaymentRequest(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public void populateAndSavePaymentRequest(PaymentRequestDocument preq) throws WorkflowException {
try {
preq.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_IN_PROCESS);
documentService.saveDocument(preq, AttributedContinuePurapEvent.class);
}
catch (ValidationException ve) {
preq.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_INITIATE);
}
catch (WorkflowException we) {
preq.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_INITIATE);
String errorMsg = "Error saving document # " + preq.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
LOG.error(errorMsg, we);
throw new RuntimeException(errorMsg, we);
}
}
/**
* If the full document entry has been completed and the status of the related purchase order document is closed, return true,
* otherwise return false.
*
* @param apDoc The AccountsPayableDocument to be determined whether its purchase order should be reversed.
* @return boolean true if the purchase order should be reversed.
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#shouldPurchaseOrderBeReversed
* (org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
@NonTransactional
public boolean shouldPurchaseOrderBeReversed(AccountsPayableDocument apDoc) {
PurchaseOrderDocument po = apDoc.getPurchaseOrderDocument();
if (ObjectUtils.isNull(po)) {
throw new RuntimeException("po should never be null on PREQ");
}
// if past full entry and already closed return true
if (purapService.isFullDocumentEntryCompleted(apDoc) && StringUtils.equalsIgnoreCase(PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED, po.getApplicationDocumentStatus())) {
return true;
}
return false;
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#getPersonForCancel(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
@NonTransactional
public Person getPersonForCancel(AccountsPayableDocument apDoc) {
PaymentRequestDocument preqDoc = (PaymentRequestDocument) apDoc;
Person user = null;
if (preqDoc.isPaymentRequestedCancelIndicator()) {
user = preqDoc.getLastActionPerformedByUser();
}
return user;
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#takePurchaseOrderCancelAction(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
@NonTransactional
public void takePurchaseOrderCancelAction(AccountsPayableDocument apDoc) {
PaymentRequestDocument preqDocument = (PaymentRequestDocument) apDoc;
if (preqDocument.isReopenPurchaseOrderIndicator()) {
String docType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT;
purchaseOrderService.createAndRoutePotentialChangeDocument(preqDocument.getPurchaseOrderDocument().getDocumentNumber(), docType, "reopened by Credit Memo " + apDoc.getPurapDocumentIdentifier() + "cancel", new ArrayList(), PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_REOPEN);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#updateStatusByNode(java.lang.String,
* org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
@NonTransactional
public String updateStatusByNode(String currentNodeName, AccountsPayableDocument apDoc) {
return updateStatusByNode(currentNodeName, (PaymentRequestDocument) apDoc);
}
/**
* Updates the status of the payment request document.
*
* @param currentNodeName The current node name.
* @param preqDoc The payment request document whose status to be updated.
* @return The canceled status code.
*/
protected String updateStatusByNode(String currentNodeName, PaymentRequestDocument preqDoc) {
// remove request cancel if necessary
clearRequestCancelFields(preqDoc);
// update the status on the document
String cancelledStatus = "";
if (StringUtils.isEmpty(currentNodeName)) {
// if empty probably not coming from workflow
cancelledStatus = PurapConstants.PaymentRequestStatuses.APPDOC_CANCELLED_POST_AP_APPROVE;
}
else {
cancelledStatus = PurapConstants.PaymentRequestStatuses.getPaymentRequestAppDocDisapproveStatuses().get(currentNodeName);
}
if (StringUtils.isNotBlank(cancelledStatus)) {
try {
preqDoc.updateAndSaveAppDocStatus(cancelledStatus);
} catch (WorkflowException we) {
throw new RuntimeException("Unable to save the route status data for document: " + preqDoc.getDocumentNumber(), we);
}
purapService.saveDocumentNoValidation(preqDoc);
}
else {
logAndThrowRuntimeException("No status found to set for document being disapproved in node '" + currentNodeName + "'");
}
return cancelledStatus;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#markPaid(org.kuali.kfs.module.purap.document.PaymentRequestDocument,
* java.sql.Date)
*/
@Override
@NonTransactional
public void markPaid(PaymentRequestDocument pr, Date processDate) {
LOG.debug("markPaid() started");
pr.setPaymentPaidTimestamp(new Timestamp(processDate.getTime()));
purapService.saveDocumentNoValidation(pr);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#hasDiscountItem(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public boolean hasDiscountItem(PaymentRequestDocument preq) {
return ObjectUtils.isNotNull(findDiscountItem(preq));
}
/**
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#poItemEligibleForAp(org.kuali.kfs.module.purap.document.AccountsPayableDocument,
* org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem)
*/
@Override
@NonTransactional
public boolean poItemEligibleForAp(AccountsPayableDocument apDoc, PurchaseOrderItem poi) {
if (ObjectUtils.isNull(poi)) {
throw new RuntimeException("item null in purchaseOrderItemEligibleForPayment ... this should never happen");
}
// if the po item is not active... skip it
if (!poi.isItemActiveIndicator()) {
return false;
}
ItemType poiType = poi.getItemType();
if (ObjectUtils.isNull(poiType)) {
return false;
}
if (poiType.isQuantityBasedGeneralLedgerIndicator()) {
if (poi.getItemQuantity().isGreaterThan(poi.getItemInvoicedTotalQuantity())) {
return true;
}
return false;
}
else { // not quantity based
// As long as it contains a number (whether it's 0, negative or positive number), we'll
// have to return true. This is so that the OutstandingEncumberedAmount and the
// Original Amount from PO column would appear on the page for Trade In.
if (poi.getItemOutstandingEncumberedAmount() != null) {
return true;
}
return false;
}
}
@Override
@NonTransactional
public void removeIneligibleAdditionalCharges(PaymentRequestDocument document) {
List<PaymentRequestItem> itemsToRemove = new ArrayList<PaymentRequestItem>();
for (PaymentRequestItem item : (List<PaymentRequestItem>) document.getItems()) {
// if no extended price and its an order discount or trade in, remove
if (ObjectUtils.isNull(item.getPurchaseOrderItemUnitPrice()) && (ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE.equals(item.getItemTypeCode()) || ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE.equals(item.getItemTypeCode()))) {
itemsToRemove.add(item);
continue;
}
// if a payment terms discount exists but not set on teh doc, remove
if (StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) {
PaymentTermType pt = document.getVendorPaymentTerms();
if ((pt != null) && (pt.getVendorPaymentTermsPercent() != null) && (BigDecimal.ZERO.compareTo(pt.getVendorPaymentTermsPercent()) != 0)) {
// discount ok
}
else {
// remove discount
itemsToRemove.add(item);
}
continue;
}
}
// remove items marked for removal
for (PaymentRequestItem item : itemsToRemove) {
document.getItems().remove(item);
}
}
@Override
@NonTransactional
public void changeVendor(PaymentRequestDocument preq, Integer headerId, Integer detailId) {
VendorDetail primaryVendor = vendorService.getVendorDetail(preq.getOriginalVendorHeaderGeneratedIdentifier(), preq.getOriginalVendorDetailAssignedIdentifier());
if (primaryVendor == null) {
LOG.error("useAlternateVendor() primaryVendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
throw new PurError("AlternateVendor: VendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
}
// set vendor detail
VendorDetail vd = vendorService.getVendorDetail(headerId, detailId);
if (vd == null) {
LOG.error("changeVendor() VendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
throw new PurError("changeVendor: VendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
}
preq.setVendorDetail(vd);
preq.setVendorName(vd.getVendorName());
preq.setVendorNumber(vd.getVendorNumber());
preq.setVendorHeaderGeneratedIdentifier(vd.getVendorHeaderGeneratedIdentifier());
preq.setVendorDetailAssignedIdentifier(vd.getVendorDetailAssignedIdentifier());
preq.setVendorPaymentTermsCode(vd.getVendorPaymentTermsCode());
preq.setVendorShippingPaymentTermsCode(vd.getVendorShippingPaymentTermsCode());
preq.setVendorShippingTitleCode(vd.getVendorShippingTitleCode());
preq.refreshReferenceObject("vendorPaymentTerms");
preq.refreshReferenceObject("vendorShippingPaymentTerms");
// Set vendor address
String deliveryCampus = preq.getPurchaseOrderDocument().getDeliveryCampusCode();
VendorAddress va = vendorService.getVendorDefaultAddress(headerId, detailId, VendorConstants.AddressTypes.REMIT, deliveryCampus);
if (va == null) {
va = vendorService.getVendorDefaultAddress(headerId, detailId, VendorConstants.AddressTypes.PURCHASE_ORDER, deliveryCampus);
}
if (va == null) {
LOG.error("changeVendor() VendorAddress from database for header id " + headerId + " and detail id " + detailId + "is null");
throw new PurError("changeVendor VendorAddress from database for header id " + headerId + " and detail id " + detailId + "is null");
}
if (preq != null) {
setVendorAddress(va, preq);
}
else {
LOG.error("changeVendor(): Null link back to the Purchase Order.");
throw new PurError("Null link back to the Purchase Order.");
}
// change document description
preq.getDocumentHeader().setDocumentDescription(createPreqDocumentDescription(preq.getPurchaseOrderIdentifier(), preq.getVendorName()));
}
/**
* Set the Vendor address of the given ID.
*
* @param addressID ID of the address to set
* @param pr PaymentRequest to set in
* @return New PaymentRequest to use
*/
protected void setVendorAddress(VendorAddress va, PaymentRequestDocument preq) {
if (va != null) {
preq.setVendorAddressGeneratedIdentifier(va.getVendorAddressGeneratedIdentifier());
preq.setVendorAddressInternationalProvinceName(va.getVendorAddressInternationalProvinceName());
preq.setVendorLine1Address(va.getVendorLine1Address());
preq.setVendorLine2Address(va.getVendorLine2Address());
preq.setVendorCityName(va.getVendorCityName());
preq.setVendorStateCode(va.getVendorStateCode());
preq.setVendorPostalCode(va.getVendorZipCode());
preq.setVendorCountryCode(va.getVendorCountryCode());
}
}
/**
* Records the specified error message into the Log file and throws a runtime exception.
*
* @param errorMessage the error message to be logged.
*/
protected void logAndThrowRuntimeException(String errorMessage) {
this.logAndThrowRuntimeException(errorMessage, null);
}
/**
* Records the specified error message into the Log file and throws the specified runtime exception.
*
* @param errorMessage the specified error message.
* @param e the specified runtime exception.
*/
protected void logAndThrowRuntimeException(String errorMessage, Exception e) {
if (ObjectUtils.isNotNull(e)) {
LOG.error(errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
else {
LOG.error(errorMessage);
throw new RuntimeException(errorMessage);
}
}
/**
* The given document here actually needs to be a Payment Request.
*
* @see org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService#generateGLEntriesCreateAccountsPayableDocument(org.kuali.kfs.module.purap.document.AccountsPayableDocument)
*/
@Override
@NonTransactional
public void generateGLEntriesCreateAccountsPayableDocument(AccountsPayableDocument apDocument) {
PaymentRequestDocument paymentRequest = (PaymentRequestDocument) apDocument;
// JHK: this is not being injected because it would cause a circular reference in the Spring definitions
SpringContext.getBean(PurapGeneralLedgerService.class).generateEntriesCreatePaymentRequest(paymentRequest);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#hasActivePaymentRequestsForPurchaseOrder(java.lang.Integer)
*/
@Override
@NonTransactional
public boolean hasActivePaymentRequestsForPurchaseOrder(Integer purchaseOrderIdentifier) {
boolean hasActivePreqs = false;
List<PaymentRequestDocument> preqs = paymentRequestDao.getActivePaymentRequestDocumentNumbersForPurchaseOrder(purchaseOrderIdentifier);
WorkflowDocument workflowDocument = null;
// docNumbers = paymentRequestDao.getActivePaymentRequestDocumentNumbersForPurchaseOrder(purchaseOrderIdentifier);
//docNumbers = filterPaymentRequestByAppDocStatus(docNumbers, PaymentRequestStatuses.STATUSES_POTENTIALLY_ACTIVE);
for (PaymentRequestDocument preq : preqs) {
if (preq.getApplicationDocumentStatus().equals(PaymentRequestStatuses.STATUSES_POTENTIALLY_ACTIVE)) {
try {
workflowDocument = workflowDocumentService.loadWorkflowDocument(preq.getDocumentNumber(), GlobalVariables.getUserSession().getPerson());
}
catch (WorkflowException we) {
throw new RuntimeException(we);
}
// if the document is not in a non-active status then return true and stop evaluation
if (!(workflowDocument.isCanceled() || workflowDocument.isException())) {
hasActivePreqs = true;
break;
}
}
}
return hasActivePreqs;
}
/**
* This method was added as part of the move to rice20 as a way to get at application doc status. Since
* this data has been moved back into KFS this function is no longer necessary. The code will be removed
* in the 6.0 release.
*/
@Deprecated
protected List<String> getPaymentRequestDocNumberForAutoApprove() {
Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight();
return paymentRequestDao.getEligibleForAutoApproval(todayAtMidnight);
}
/**
* Filter the results by application doc status
*
* @param lookupDocNumbers
* @param appDocStatus
* @return a list of document numbers matching application document status based on
*/
protected void filterPaymentRequestByAppDocStatus(Map<String, String> paymentRequestResults, List<String> lookupDocNumbers, String... applicationDocumentStatus) {
List<String> paymentRequestDocNumbersInclude = new ArrayList<String>();
List<String> paymentRequestDocNumbersExclude = new ArrayList<String>();
for (String docId : lookupDocNumbers) {
try {
PaymentRequestDocument preq = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docId);
if(Arrays.asList(applicationDocumentStatus).contains(preq.getApplicationDocumentStatus())) {
paymentRequestDocNumbersInclude.add(docId);
} else {
paymentRequestDocNumbersExclude.add(docId);
}
}
catch (WorkflowException ex) {
LOG.warn( "Error retrieving doc for doc #" + docId + ". This shouldn't happen.", ex );
throw new RuntimeException(ex.getMessage(),ex);
}
}
if (!paymentRequestDocNumbersInclude.isEmpty()) {
paymentRequestResults.put("hasInProcess", "Y");
}
if (!paymentRequestDocNumbersExclude.isEmpty()) {
paymentRequestResults.put("checkInProcess", "Y");
}
}
/**
* Wrapper class to the filterPaymentRequestByAppDocStatus
*
* This class first extract the payment request document numbers from the Payment Request Collections,
* then perform the filterPaymentRequestByAppDocStatus function. Base on the filtered payment request
* doc number, reconstruct the filtered Payment Request Collection
*
* @param paymentRequestDocuments
* @param appDocStatus
* @return
*/
protected Collection<PaymentRequestDocument> filterPaymentRequestByAppDocStatus(Collection<PaymentRequestDocument> paymentRequestDocuments, String... appDocStatus) {
Collection<PaymentRequestDocument> filteredPaymentRequestDocuments = new ArrayList<PaymentRequestDocument>();
List status = Arrays.asList(appDocStatus);
for (PaymentRequestDocument paymentRequest : paymentRequestDocuments){
long start = System.currentTimeMillis();
if (status.contains(paymentRequest.getApplicationDocumentStatus())) {
filteredPaymentRequestDocuments.add(paymentRequest);
}
if (LOG.isDebugEnabled()) {
LOG.debug(System.currentTimeMillis()-start +"ms to check app doc status");
}
}
return filteredPaymentRequestDocuments;
}
/**
* Wrapper class to the filterPaymentRequestByAppDocStatus (Collection<PaymentRequestDocument>)
*
* This class first construct the Payment Request Collection from the iterator, and then process through
* filterPaymentRequestByAppDocStatus
*
* @param paymentRequestDocuments
* @param appDocStatus
* @return
*/
protected Iterator<PaymentRequestDocument> filterPaymentRequestByAppDocStatus(Iterator<PaymentRequestDocument> paymentRequestIterator, String... appDocStatus) {
Collection<PaymentRequestDocument> paymentRequestDocuments = new ArrayList<PaymentRequestDocument>();
for (;paymentRequestIterator.hasNext();){
paymentRequestDocuments.add(paymentRequestIterator.next());
}
return filterPaymentRequestByAppDocStatus(paymentRequestDocuments, appDocStatus).iterator();
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#processPaymentRequestInReceivingStatus()
*/
@Override
@NonTransactional
public void processPaymentRequestInReceivingStatus() {
List<PaymentRequestDocument> preqs = paymentRequestDao.getPaymentRequestInReceivingStatus();
// docNumbers = filterPaymentRequestByAppDocStatus(docNumbers, PurapConstants.PaymentRequestStatuses.APPDOC_AWAITING_RECEIVING_REVIEW);
List<PaymentRequestDocument> preqsAwaitingReceiving = new ArrayList<PaymentRequestDocument>();
for (PaymentRequestDocument preq : preqs) {
//PaymentRequestDocument preq = getPaymentRequestByDocumentNumber(docNumber);
if (ObjectUtils.isNotNull(preq)) {
preqsAwaitingReceiving.add(preq);
}
}
//if (ObjectUtils.isNotNull(preqsAwaitingReceiving)) {
for (PaymentRequestDocument preqDoc : preqsAwaitingReceiving) {
if (preqDoc.isReceivingRequirementMet() && preqDoc.getApplicationDocumentStatus().equals(PaymentRequestStatuses.APPDOC_AWAITING_RECEIVING_REVIEW)) {
try {
documentService.approveDocument(preqDoc, "Approved by Receiving Required PREQ job", null);
}
catch (WorkflowException e) {
LOG.error("processPaymentRequestInReceivingStatus() Error approving payment request document from awaiting receiving", e);
throw new RuntimeException("Error approving payment request document from awaiting receiving", e);
}
}
}
// }
}
/**
* @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#allowBackpost(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
*/
@Override
@NonTransactional
public boolean allowBackpost(PaymentRequestDocument paymentRequestDocument) {
int allowBackpost = (Integer.parseInt(parameterService.getParameterValueAsString(PaymentRequestDocument.class, PurapRuleConstants.ALLOW_BACKPOST_DAYS)));
Calendar today = dateTimeService.getCurrentCalendar();
Integer currentFY = universityDateService.getCurrentUniversityDate().getUniversityFiscalYear();
java.util.Date priorClosingDateTemp = universityDateService.getLastDateOfFiscalYear(currentFY - 1);
Calendar priorClosingDate = Calendar.getInstance();
priorClosingDate.setTime(priorClosingDateTemp);
// adding 1 to set the date to midnight the day after backpost is allowed so that preqs allow backpost on the last day
Calendar allowBackpostDate = Calendar.getInstance();
allowBackpostDate.setTime(priorClosingDate.getTime());
allowBackpostDate.add(Calendar.DATE, allowBackpost + 1);
Calendar preqInvoiceDate = Calendar.getInstance();
preqInvoiceDate.setTime(paymentRequestDocument.getInvoiceDate());
// if today is after the closing date but before/equal to the allowed backpost date and the invoice date is for the
// prior year, set the year to prior year
if ((today.compareTo(priorClosingDate) > 0) && (today.compareTo(allowBackpostDate) <= 0) && (preqInvoiceDate.compareTo(priorClosingDate) <= 0)) {
LOG.debug("allowBackpost() within range to allow backpost; posting entry to period 12 of previous FY");
return true;
}
LOG.debug("allowBackpost() not within range to allow backpost; posting entry to current FY");
return false;
}
@Override
@NonTransactional
public boolean isPurchaseOrderValidForPaymentRequestDocumentCreation(PaymentRequestDocument paymentRequestDocument, PurchaseOrderDocument po) {
Integer POID = paymentRequestDocument.getPurchaseOrderIdentifier();
boolean valid = true;
PurchaseOrderDocument purchaseOrderDocument = paymentRequestDocument.getPurchaseOrderDocument();
if (ObjectUtils.isNull(purchaseOrderDocument)) {
GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, PurapKeyConstants.ERROR_PURCHASE_ORDER_NOT_EXIST);
valid &= false;
}
else if (purchaseOrderDocument.isPendingActionIndicator()) {
GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, PurapKeyConstants.ERROR_PURCHASE_PENDING_ACTION);
valid &= false;
}
else if (!StringUtils.equals(purchaseOrderDocument.getApplicationDocumentStatus(), PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN)) {
GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, PurapKeyConstants.ERROR_PURCHASE_ORDER_NOT_OPEN);
valid &= false;
// if the PO is pending and it is not a Retransmit, we cannot generate a Payment Request for it
}
else {
// Verify that there exists at least 1 item left to be invoiced
// valid &= encumberedItemExistsForInvoicing(purchaseOrderDocument);
}
return valid;
}
@Override
@NonTransactional
public boolean encumberedItemExistsForInvoicing(PurchaseOrderDocument document) {
boolean zeroDollar = true;
GlobalVariables.getMessageMap().clearErrorPath();
GlobalVariables.getMessageMap().addToErrorPath(KFSPropertyConstants.DOCUMENT);
for (PurchaseOrderItem poi : (List<PurchaseOrderItem>) document.getItems()) {
// Quantity-based items
if (poi.getItemType().isLineItemIndicator() && poi.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
KualiDecimal encumberedQuantity = poi.getItemOutstandingEncumberedQuantity() == null ? KualiDecimal.ZERO : poi.getItemOutstandingEncumberedQuantity();
if (encumberedQuantity.compareTo(KualiDecimal.ZERO) == 1) {
zeroDollar = false;
break;
}
}
// Service Items or Below-the-line Items
else if (poi.getItemType().isAmountBasedGeneralLedgerIndicator() || poi.getItemType().isAdditionalChargeIndicator()) {
KualiDecimal encumberedAmount = poi.getItemOutstandingEncumberedAmount() == null ? KualiDecimal.ZERO : poi.getItemOutstandingEncumberedAmount();
if (encumberedAmount.compareTo(KualiDecimal.ZERO) == 1) {
zeroDollar = false;
break;
}
}
}
return !zeroDollar;
}
@NonTransactional
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
@NonTransactional
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
@NonTransactional
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
@NonTransactional
public void setDocumentService(DocumentService documentService) {
this.documentService = documentService;
}
@NonTransactional
public void setNoteService(NoteService noteService) {
this.noteService = noteService;
}
@NonTransactional
public void setPurapService(PurapService purapService) {
this.purapService = purapService;
}
@NonTransactional
public void setPaymentRequestDao(PaymentRequestDao paymentRequestDao) {
this.paymentRequestDao = paymentRequestDao;
}
@NonTransactional
public void setNegativePaymentRequestApprovalLimitService(NegativePaymentRequestApprovalLimitService negativePaymentRequestApprovalLimitService) {
this.negativePaymentRequestApprovalLimitService = negativePaymentRequestApprovalLimitService;
}
@NonTransactional
public void setPurapAccountingService(PurapAccountingService purapAccountingService) {
this.purapAccountingService = purapAccountingService;
}
@NonTransactional
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
@NonTransactional
public void setPurapWorkflowIntegrationService(PurApWorkflowIntegrationService purapWorkflowIntegrationService) {
this.purapWorkflowIntegrationService = purapWorkflowIntegrationService;
}
@NonTransactional
public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
this.workflowDocumentService = workflowDocumentService;
}
@NonTransactional
public void setAccountsPayableService(AccountsPayableService accountsPayableService) {
this.accountsPayableService = accountsPayableService;
}
@NonTransactional
public void setVendorService(VendorService vendorService) {
this.vendorService = vendorService;
}
@NonTransactional
public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
this.dataDictionaryService = dataDictionaryService;
}
@NonTransactional
public void setUniversityDateService(UniversityDateService universityDateService) {
this.universityDateService = universityDateService;
}
@NonTransactional
public void setBankService(BankService bankService) {
this.bankService = bankService;
}
@NonTransactional
public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
this.purchaseOrderService = purchaseOrderService;
}
@NonTransactional
public void setFinancialSystemWorkflowHelperService(FinancialSystemWorkflowHelperService financialSystemWorkflowHelperService) {
this.financialSystemWorkflowHelperService = financialSystemWorkflowHelperService;
}
@NonTransactional
public void setKualiRuleService(KualiRuleService kualiRuleService) {
this.kualiRuleService = kualiRuleService;
}
/**
* Gets the accountsPayableService attribute.
*
* @return Returns the accountsPayableService
*/
@NonTransactional
public AccountsPayableService getAccountsPayableService() {
return SpringContext.getBean(AccountsPayableService.class);
}
/**
* @return Returns the personService.
*/
protected PersonService getPersonService() {
if(personService==null) {
personService = SpringContext.getBean(PersonService.class);
}
return personService;
}
}