Java tutorial
/* * Copyright 2007 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.ole.module.purap.document.service.impl; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.text.StrBuilder; import org.kuali.ole.module.purap.*; import org.kuali.ole.module.purap.PurapConstants.InvoiceStatuses; import org.kuali.ole.module.purap.PurapConstants.ItemTypeCodes; import org.kuali.ole.module.purap.PurapConstants.PRQSDocumentsStrings; import org.kuali.ole.module.purap.PurapParameterConstants.NRATaxParameters; import org.kuali.ole.module.purap.businessobject.*; import org.kuali.ole.module.purap.document.*; import org.kuali.ole.module.purap.document.dataaccess.InvoiceDao; import org.kuali.ole.module.purap.document.service.*; import org.kuali.ole.module.purap.document.validation.event.AttributedContinuePurapEvent; import org.kuali.ole.module.purap.exception.PurError; import org.kuali.ole.module.purap.service.PurapAccountingService; import org.kuali.ole.module.purap.service.PurapGeneralLedgerService; import org.kuali.ole.module.purap.util.PurApItemUtils; import org.kuali.ole.module.purap.util.VendorGroupingHelper; import org.kuali.ole.select.OleSelectConstant; import org.kuali.ole.sys.OLEConstants; import org.kuali.ole.sys.OLEPropertyConstants; import org.kuali.ole.sys.businessobject.AccountingLine; import org.kuali.ole.sys.businessobject.SourceAccountingLine; import org.kuali.ole.sys.context.SpringContext; import org.kuali.ole.sys.service.BankService; import org.kuali.ole.sys.service.FinancialSystemWorkflowHelperService; import org.kuali.ole.sys.service.UniversityDateService; import org.kuali.ole.sys.service.impl.OleParameterConstants; import org.kuali.ole.vnd.VendorConstants; import org.kuali.ole.vnd.businessobject.PaymentTermType; import org.kuali.ole.vnd.businessobject.VendorAddress; import org.kuali.ole.vnd.businessobject.VendorDetail; import org.kuali.ole.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.api.CoreServiceApiServiceLocator; import org.kuali.rice.coreservice.api.parameter.Parameter; import org.kuali.rice.coreservice.api.parameter.ParameterKey; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.KewApiServiceLocator; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria; import org.kuali.rice.kew.api.document.search.DocumentSearchResult; import org.kuali.rice.kew.api.document.search.DocumentSearchResults; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kim.api.identity.Person; 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.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; import java.math.BigDecimal; import java.sql.Date; import java.sql.Timestamp; import java.util.*; /** * This class provides services of use to a payment request document */ @Transactional public class InvoiceServiceImpl implements InvoiceService { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InvoiceServiceImpl.class); protected DateTimeService dateTimeService; protected DocumentService documentService; protected NoteService noteService; protected PurapService purapService; protected InvoiceDao invoiceDao; protected ParameterService parameterService; protected ConfigurationService configurationService; protected NegativeInvoiceApprovalLimitService negativeInvoiceApprovalLimitService; 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 boolean currencyTypeIndicator; /** * NOTE: unused * * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtractByCM(String, org.kuali.ole.module.purap.document.VendorCreditMemoDocument) */ @Override @Deprecated public Collection<InvoiceDocument> getInvoicesToExtractByCM(String campusCode, VendorCreditMemoDocument cmd) { LOG.debug("getInvoicesByCM() started"); Date currentSqlDateMidnight = dateTimeService.getCurrentSqlDateMidnight(); List<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(campusCode, null, null, cmd.getVendorHeaderGeneratedIdentifier(), cmd.getVendorDetailAssignedIdentifier(), currentSqlDateMidnight); return filterInvoiceByAppDocStatus(invoiceIterator, InvoiceStatuses.APPDOC_AUTO_APPROVED, InvoiceStatuses.APPDOC_DEPARTMENT_APPROVED); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtractByVendor(String, * org.kuali.ole.module.purap.util.VendorGroupingHelper, java.sql.Date) */ @Override public Collection<InvoiceDocument> getInvoicesToExtractByVendor(String campusCode, VendorGroupingHelper vendor, Date onOrBeforeInvoicePayDate) { LOG.debug("getInvoicesByVendor() started"); Collection<InvoiceDocument> invoiceDocuments = invoiceDao.getInvoicesToExtractForVendor(campusCode, vendor, onOrBeforeInvoicePayDate); return filterInvoiceByAppDocStatus(invoiceDocuments, InvoiceStatuses.APPDOC_AUTO_APPROVED, InvoiceStatuses.APPDOC_DEPARTMENT_APPROVED); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtract(java.sql.Date) */ @Override public Collection<InvoiceDocument> getInvoicesToExtract(Date onOrBeforeInvoicePayDate) { LOG.debug("getInvoicesToExtract() started"); Collection<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(false, null, onOrBeforeInvoicePayDate); return filterInvoiceByAppDocStatus(invoiceIterator, InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtractSpecialPayments(String, * java.sql.Date) */ @Override public Collection<InvoiceDocument> getInvoicesToExtractSpecialPayments(String chartCode, Date onOrBeforeInvoicePayDate) { LOG.debug("getInvoicesToExtractSpecialPayments() started"); Collection<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(true, chartCode, onOrBeforeInvoicePayDate); return filterInvoiceByAppDocStatus(invoiceIterator, InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getImmediateInvoicesToExtract(String) */ @Override public Collection<InvoiceDocument> getImmediateInvoicesToExtract(String chartCode) { LOG.debug("getImmediateInvoicesToExtract() started"); Collection<InvoiceDocument> invoiceIterator = invoiceDao.getImmediateInvoicesToExtract(chartCode); return filterInvoiceByAppDocStatus(invoiceIterator, InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoiceToExtractByChart(String, * java.sql.Date) */ @Override public Collection<InvoiceDocument> getInvoiceToExtractByChart(String chartCode, Date onOrBeforeInvoicePayDate) { LOG.debug("getInvoiceToExtractByChart() started"); Collection<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(false, chartCode, onOrBeforeInvoicePayDate); return filterInvoiceByAppDocStatus(invoiceIterator, InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#autoApproveInvoices() */ @Override public boolean autoApproveInvoices() { if (LOG.isInfoEnabled()) { LOG.info("Starting autoApproveInvoices."); } boolean hadErrorAtLeastOneError = true; // should objects from existing user session be copied over Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight(); List<String> docNumbers = invoiceDao.getEligibleForAutoApproval(todayAtMidnight); docNumbers = filterInvoiceByAppDocStatus(docNumbers, InvoiceStatuses.PRQS_STATUSES_FOR_AUTO_APPROVE); if (LOG.isInfoEnabled()) { LOG.info(" -- Initial filtering complete, returned " + new Integer(docNumbers.size()).toString() + " docs."); } List<InvoiceDocument> docs = new ArrayList<InvoiceDocument>(); for (String docNumber : docNumbers) { InvoiceDocument prqs = getInvoiceByDocumentNumber(docNumber); if (ObjectUtils.isNotNull(prqs)) { docs.add(prqs); } } if (LOG.isInfoEnabled()) { LOG.info(" -- Initial filtering complete, returned " + new Integer((docs == null ? 0 : docs.size())).toString() + " docs."); } if (docs != null) { String samt = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, 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() + "."); } for (InvoiceDocument invoiceDocument : docs) { hadErrorAtLeastOneError |= !autoApproveInvoice(invoiceDocument, 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.ole.module.purap.document.service.InvoiceService#autoApproveInvoice(String, * org.kuali.rice.core.api.util.type.KualiDecimal) */ @Override public boolean autoApproveInvoice(String docNumber, KualiDecimal defaultMinimumLimit) { InvoiceDocument invoiceDocument = null; try { invoiceDocument = (InvoiceDocument) documentService.getByDocumentHeaderId(docNumber); if (invoiceDocument.isHoldIndicator() || invoiceDocument.isInvoiceCancelIndicator() || !Arrays.asList(InvoiceStatuses.PRQS_STATUSES_FOR_AUTO_APPROVE) .contains(invoiceDocument.getApplicationDocumentStatus())) { // this condition is based on the conditions that InvoiceDaoOjb.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("Invoice Document " + invoiceDocument.getDocumentNumber() + " could not be auto-approved because it has either been placed on hold, " + " requested cancel, or does not have one of the PRQS statuses for auto-approve."); return true; } if (autoApproveInvoice(invoiceDocument, defaultMinimumLimit)) { if (LOG.isInfoEnabled()) { LOG.info("Auto-approval for payment request successful. Doc number: " + docNumber); } return true; } else { LOG.error("Invoice 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.ole.module.purap.document.service.InvoiceService#autoApproveInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, * org.kuali.rice.core.api.util.type.KualiDecimal) */ @Override public boolean autoApproveInvoice(InvoiceDocument 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 (InvoiceItem item : (List<InvoiceItem>) doc.getItems()) { ObjectUtils.materializeUpdateableCollections(item); } } catch (Exception ex) { throw new RuntimeException(ex); } doc = (InvoiceDocument) ObjectUtils.deepCopy(doc); //purapService.updateStatus(doc, InvoiceStatuses.AUTO_APPROVED); doc.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_AUTO_APPROVED); documentService.blanketApproveDocument(doc, "auto-approving: Total is below threshold.", null); } 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; } /** * 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.ole.module.purap.document.service.PaymentRequestService#autoApprovePaymentRequest(java.lang.String, * org.kuali.rice.core.api.util.type.KualiDecimal) */ @Override public boolean autoApprovePaymentRequest(String docNumber) { 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)) { 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.ole.module.purap.document.service.PaymentRequestService#autoApprovePaymentRequest(org.kuali.ole.module.purap.document.PaymentRequestDocument, * org.kuali.rice.core.api.util.type.KualiDecimal) */ public boolean autoApprovePaymentRequest(PaymentRequestDocument doc) { 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); //purapService.updateStatus(doc, PaymentRequestStatuses.AUTO_APPROVED); doc.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_AUTO_APPROVED); documentService.blanketApproveDocument(doc, "auto-approving: Document Created from Invoice.", null); } 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(InvoiceDocument document, KualiDecimal defaultMinimumLimit) { // Check if vendor is foreign. if (document.getVendorDetail().getCurrencyType() != null) { if (document.getVendorDetail().getCurrencyType().getCurrencyType() .equalsIgnoreCase(OleSelectConstant.CURRENCY_TYPE_NAME)) { currencyTypeIndicator = true; } else { currencyTypeIndicator = false; } } if (!currencyTypeIndicator) { 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, InvoiceStatuses.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.isInvoicePositiveApprovalIndicator()) { 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. for (SourceAccountingLine line : purapAccountingService .generateSummary((List<PurApItem>) document.getItems())) { // 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( negativeInvoiceApprovalLimitService.findByChart(line.getChartOfAccountsCode()), minimumAmount); minimumAmount = getMinimumLimitAmount(negativeInvoiceApprovalLimitService .findByChartAndAccount(line.getChartOfAccountsCode(), line.getAccountNumber()), minimumAmount); minimumAmount = getMinimumLimitAmount(negativeInvoiceApprovalLimitService.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 NegativeInvoiceApprovalLimit to be used in determining the minimum limit amount. * @param minimumAmount The amount to be compared with the collection of NegativeInvoiceApprovalLimit 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<NegativeInvoiceApprovalLimit> limits, KualiDecimal minimumAmount) { for (NegativeInvoiceApprovalLimit limit : limits) { KualiDecimal amount = limit.getNegativeInvoiceApprovalLimitAmount(); 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. * @return List of payment request document. */ @Override public List getInvoicesByVendorNumber(Integer vendorHeaderGeneratedId, Integer vendorDetailAssignedId) { LOG.debug("getActiveInvoicesByVendorNumber() started"); return invoiceDao.getActiveInvoicesByVendorNumber(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 public List getInvoicesByVendorNumberInvoiceNumber(Integer vendorHeaderGeneratedId, Integer vendorDetailAssignedId, String invoiceNumber) { LOG.debug("getActiveInvoicesByVendorNumberInvoiceNumber() started"); return invoiceDao.getActiveInvoicesByVendorNumberInvoiceNumber(vendorHeaderGeneratedId, vendorDetailAssignedId, invoiceNumber); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#invoiceDuplicateMessages(org.kuali.ole.module.purap.document.InvoiceDocument) */ @Override public HashMap<String, String> invoiceDuplicateMessages(InvoiceDocument 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(PRQSDocumentsStrings.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<InvoiceDocument> prqss = new ArrayList(); List<InvoiceDocument> prqssDuplicates = getInvoicesByVendorNumber(vendorHeaderGeneratedId, vendorDetailAssignedId); for (InvoiceDocument duplicatePRQS : prqssDuplicates) { if (duplicatePRQS.getInvoiceNumber().toUpperCase() .equals(document.getInvoiceNumber().toUpperCase())) { // found the duplicate row... so add to the prqss list... prqss.add(duplicatePRQS); } } if (prqss.size() > 0) { boolean addedMessage = false; boolean foundCanceledPostApprove = false; // cancelled // boolean foundCanceledPreApprove = false; // voided for (InvoiceDocument testPRQS : prqss) { if (StringUtils.equals(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_POST_AP_APPROVE)) { foundCanceledPostApprove |= true; } /*else if (StringUtils.equals(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_IN_PROCESS)) { foundCanceledPreApprove |= true; }*/ else { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService .getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE)); addedMessage = true; break; } } // Custom error message for duplicates related to cancelled/voided PRQSs if (!addedMessage) { /*if (foundCanceledPostApprove && foundCanceledPreApprove) { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLEDORVOIDED)); } else if (foundCanceledPreApprove) { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_VOIDED)); } */ if (foundCanceledPostApprove) { // messages.add("errors.duplicate.vendor.invoice.cancelled"); msgs.put(PRQSDocumentsStrings.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 PRQSs for this PO prqss = getInvoicesByPOIdInvoiceAmountInvoiceDate(purchaseOrderId, document.getVendorInvoiceAmount(), document.getInvoiceDate()); if (prqss.size() > 0) { boolean addedMessage = false; boolean foundCanceledPostApprove = false; // cancelled // boolean foundCanceledPreApprove = false; // voided msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService .getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT)); for (InvoiceDocument testPRQS : prqss) { if (StringUtils.equalsIgnoreCase(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_POST_AP_APPROVE)) { foundCanceledPostApprove |= true; } /*else if (StringUtils.equalsIgnoreCase(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_IN_PROCESS)) { foundCanceledPreApprove |= true; }*/ else { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService .getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT)); addedMessage = true; break; } } // Custom error message for duplicates related to cancelled/voided PRQSs if (!addedMessage) { /* if (foundCanceledPostApprove && foundCanceledPreApprove) { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLEDORVOIDED)); } else if (foundCanceledPreApprove) { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_VOIDED)); addedMessage = true; }*/ if (foundCanceledPostApprove) { msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString( PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLED)); addedMessage = true; } } } } return msgs; } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoiceByDocumentNumber(String) */ @Override public InvoiceDocument getInvoiceByDocumentNumber(String documentNumber) { LOG.debug("getInvoiceByDocumentNumber() started"); if (ObjectUtils.isNotNull(documentNumber)) { try { InvoiceDocument doc = (InvoiceDocument) documentService.getByDocumentHeaderId(documentNumber); return doc; } catch (WorkflowException e) { String errorMessage = "Error getting payment request document from document service"; LOG.error("getInvoiceByDocumentNumber() " + errorMessage, e); throw new RuntimeException(errorMessage, e); } } return null; } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoiceById(Integer) */ @Override public InvoiceDocument getInvoiceById(Integer poDocId) { return getInvoiceByDocumentNumber(invoiceDao.getDocumentNumberByInvoiceId(poDocId)); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesByPurchaseOrderId(Integer) */ @Override public List<InvoiceDocument> getInvoicesByPurchaseOrderId(Integer poDocId) { List<InvoiceDocument> prqss = new ArrayList<InvoiceDocument>(); List<String> docNumbers = invoiceDao.getDocumentNumbersByPurchaseOrderId(poDocId); for (String docNumber : docNumbers) { InvoiceDocument prqs = getInvoiceByDocumentNumber(docNumber); if (ObjectUtils.isNotNull(prqs)) { prqss.add(prqs); } } return prqss; } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesByPOIdInvoiceAmountInvoiceDate(Integer, * org.kuali.rice.core.api.util.type.KualiDecimal, java.sql.Date) */ @Override public List<InvoiceDocument> getInvoicesByPOIdInvoiceAmountInvoiceDate(Integer poId, KualiDecimal invoiceAmount, Date invoiceDate) { LOG.debug("getInvoicesByPOIdInvoiceAmountInvoiceDate() started"); return invoiceDao.getActiveInvoicesByPOIdInvoiceAmountInvoiceDate(poId, invoiceAmount, invoiceDate); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#isInvoiceDateAfterToday(java.sql.Date) */ @Override 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.ole.module.purap.document.service.InvoiceService#calculatePayDate(java.sql.Date, * org.kuali.ole.vnd.businessobject.PaymentTermType) */ @Override public Date calculatePayDate(Date invoiceDate, PaymentTermType terms) { LOG.debug("calculatePayDate() started"); // calculate the invoice + processed calendar // invoice date mandatory validations will be done at submit , in future calculate will come under submit if (invoiceDate == null) { return null; } Calendar invoicedDateCalendar = dateTimeService.getCalendar(invoiceDate); Calendar processedDateCalendar = dateTimeService.getCurrentCalendar(); // add default number of days to processed String defaultDays = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, PurapParameterConstants.PURAP_PRQS_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.PRQS_PAY_DATE_EMPTY_TERMS_DEFAULT_DAYS); return returnLaterDate(invoicedDateCalendar, processedDateCalendar); } Integer discountDueNumber = terms.getVendorDiscountDueNumber(); Integer netDueNumber = terms.getVendorNetDueNumber(); if (ObjectUtils.isNotNull(discountDueNumber)) { String discountDueTypeDescription = terms.getVendorDiscountDueTypeDescription(); paymentTermsDateCalculation(discountDueTypeDescription, invoicedDateCalendar, discountDueNumber); } else if (ObjectUtils.isNotNull(netDueNumber)) { 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 Date returnLaterDate(Calendar invoicedDateCalendar, Calendar processedDateCalendar) { if (invoicedDateCalendar.after(processedDateCalendar)) { return new Date(invoicedDateCalendar.getTimeInMillis()); } else { return new 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 dueNumber Either the vendorDiscountDueNumber or the vendorDiscountDueNumber of the payment term. */ protected void paymentTermsDateCalculation(String dueTypeDescription, Calendar invoicedDateCalendar, Integer dueNumber) { if (StringUtils.equals(dueTypeDescription, PurapConstants.PRQS_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.PRQS_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.ole.module.purap.document.service.InvoiceService#calculateInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, * boolean) */ @Override public void calculateInvoice(InvoiceDocument invoice, boolean updateDiscount) { LOG.debug("calculateInvoice() started"); // general calculation, i.e. for the whole prqs document if (ObjectUtils.isNull(invoice.getInvoicePayDate())) { invoice.setInvoicePayDate(calculatePayDate(invoice.getInvoiceDate(), invoice.getVendorPaymentTerms())); } distributeAccounting(invoice); purapService.calculateTax(invoice); // do proration for full order and trade in purapService.prorateForTradeInAndFullOrderDiscount(invoice); // do proration for payment terms discount if (updateDiscount) { calculateDiscount(invoice); } distributeAccounting(invoice); } /** * Calculates the discount item for this invoice. * * @param invoiceDocument The payment request document whose discount to be calculated. */ protected void calculateDiscount(InvoiceDocument invoiceDocument) { InvoiceItem discountItem = findDiscountItem(invoiceDocument); // find out if we really need the discount item PaymentTermType pt = invoiceDocument.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(invoiceDocument); // fix up below the line items removeIneligibleAdditionalCharges(invoiceDocument); discountItem = findDiscountItem(invoiceDocument); } // Deleted the discountItem.getExtendedPrice() null and isZero InvoiceItem fullOrderItem = findFullOrderDiscountItem(invoiceDocument); 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 = invoiceDocument.getTotalPreTaxDollarAmountAboveLineItems() .add(fullOrderAmount); PurApItem tradeInItem = invoiceDocument.getTradeInItem(); if (ObjectUtils.isNotNull(tradeInItem)) { totalCost = totalCost.subtract(tradeInItem.getTotalAmount()); } BigDecimal discountAmount = pt.getVendorPaymentTermsPercent().multiply(totalCost.bigDecimalValue()) .multiply(new BigDecimal(PurapConstants.PRQS_DISCOUNT_MULT)); // do we really need to set both, not positive, but probably won't hurt discountItem.setItemUnitPrice(discountAmount.setScale(4, KualiDecimal.ROUND_BEHAVIOR)); discountItem.setExtendedPrice(new KualiDecimal(discountAmount)); // set tax amount boolean salesTaxInd = parameterService.getParameterValueAsBoolean( OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND); boolean useTaxIndicator = invoiceDocument.isUseTaxIndicator(); if (salesTaxInd == true && useTaxIndicator == false) { KualiDecimal totalTax = invoiceDocument.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(invoiceDocument); } else { // no discount if (discountItem != null) { invoiceDocument.getItems().remove(discountItem); } } } @Override public void clearTax(InvoiceDocument 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.ole.module.purap.document.service.InvoiceService#calculateTaxArea(org.kuali.ole.module.purap.document.InvoiceDocument) */ @Override public void calculateTaxArea(InvoiceDocument prqs) { LOG.debug("calculateTaxArea() started"); // remove all existing tax items added by previous calculation removeTaxItems(prqs); // don't need to calculate tax items if TaxClassificationCode is N (Non_Reportable) if (StringUtils.equalsIgnoreCase(prqs.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 = prqs.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 (prqs.getTaxGrossUpIndicator() && prqs.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) { PurApItem stateGrossItem = addTaxItem(prqs, 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 (prqs.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) { PurApItem stateTaxItem = addTaxItem(prqs, 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 (prqs.getTaxGrossUpIndicator() && prqs.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) { PurApItem federalGrossItem = addTaxItem(prqs, 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 (prqs.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) { PurApItem federalTaxItem = addTaxItem(prqs, 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 InvoiceDocument.processNodeChange to call PurapService.deleteUnenteredItems } /** * Removes all existing NRA tax items from the specified payment request. * * @param prqs The payment request from which all tax items are to be removed. */ protected void removeTaxItems(InvoiceDocument prqs) { List<PurApItem> items = prqs.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 prqs 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(InvoiceDocument prqs, String itemTypeCode, BigDecimal taxableAmount) { PurApItem taxItem = null; try { taxItem = (PurApItem) prqs.getItemClass().newInstance(); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Unable to access itemClass", e); } catch (InstantiationException e) { throw new IllegalArgumentException("Unable to instantiate itemClass", e); } // add item to prqs before adding the accounting line taxItem.setItemTypeCode(itemTypeCode); prqs.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) { InvoiceDocument prqs = taxItem.getPurapDocument(); PurApAccountingLine taxLine = null; try { taxLine = (PurApAccountingLine) taxItem.getAccountingLineClass().newInstance(); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Unable to access sourceAccountingLineClass", e); } catch (InstantiationException e) { throw new IllegalArgumentException("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 prqs's first item's first accounting line, which shall exist at this point AccountingLine line1 = prqs.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 = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX); taxAccount = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX); taxObjectCode = parameterService.getSubParameterValueAsString(InvoiceDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, prqs.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 = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX); taxAccount = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX); taxObjectCode = parameterService.getSubParameterValueAsString(InvoiceDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, prqs.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 = prqs.getTaxFederalPercent(); BigDecimal taxPercentState = prqs.getTaxStatePercent(); BigDecimal taxPercent = isFederal ? taxPercentFederal : taxPercentState; // divider value according to gross up or not BigDecimal taxDivider = new BigDecimal(100); if (prqs.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(prqs.getDocumentNumber()); taxLine.setSequenceNumber(prqs.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 invoiceDocument The payment request document to be used to find the discount item. * @return The discount item if it exists. */ protected InvoiceItem findDiscountItem(InvoiceDocument invoiceDocument) { InvoiceItem discountItem = null; for (InvoiceItem prqsItem : (List<InvoiceItem>) invoiceDocument.getItems()) { if (StringUtils.equals(prqsItem.getItemTypeCode(), ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) { discountItem = prqsItem; break; } } return discountItem; } /** * Finds the full order discount item of the payment request document. * * @param invoiceDocument The payment request document to be used to find the full order discount item. * @return The discount item if it exists. */ protected InvoiceItem findFullOrderDiscountItem(InvoiceDocument invoiceDocument) { InvoiceItem discountItem = null; for (InvoiceItem prqsItem : (List<InvoiceItem>) invoiceDocument.getItems()) { if (StringUtils.equals(prqsItem.getItemTypeCode(), ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) { discountItem = prqsItem; break; } } return discountItem; } /** * Distributes accounts for a payment request document. * * @param invoiceDocument */ protected void distributeAccounting(InvoiceDocument invoiceDocument) { // update the account amounts before doing any distribution purapAccountingService.updateAccountAmounts(invoiceDocument); String accountDistributionMethod = invoiceDocument.getAccountDistributionMethod(); for (InvoiceItem item : (List<InvoiceItem>) invoiceDocument.getItems()) { KualiDecimal totalAmount = KualiDecimal.ZERO; List<PurApAccountingLine> distributedAccounts = null; List<SourceAccountingLine> summaryAccounts = null; Set excludedItemTypeCodes = new HashSet(); excludedItemTypeCodes.add(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(ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE, item.getItemType().getItemTypeCode())) && (invoiceDocument.getGrandTotal() != null) && ((KualiDecimal.ZERO.compareTo(invoiceDocument.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 = invoiceDocument.getLineItemTotal(); // prorate item line accounts only Set includedItemTypeCodes = new HashSet(); includedItemTypeCodes.add(ItemTypeCodes.ITEM_TYPE_ITEM_CODE); includedItemTypeCodes.add(ItemTypeCodes.ITEM_TYPE_SERVICE_CODE); summaryAccounts = purapAccountingService.generateSummaryIncludeItemTypesAndNoZeroTotals( invoiceDocument.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.. //OLEMI-8487 if (summaryAccounts != null) { distributedAccounts = purapAccountingService.generateAccountDistributionForProration( summaryAccounts, totalAmount, PurapConstants.PRORATION_SCALE, InvoiceAccount.class); } // OLE-3405 : disabling the distribution method choice // 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(invoiceDocument, 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 = invoiceDocument.getPurchaseOrderDocument() .getTotalDollarAmountAboveLineItems(); purapAccountingService.updateAccountAmounts(invoiceDocument.getPurchaseOrderDocument()); summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils .getAboveTheLineOnly(invoiceDocument.getPurchaseOrderDocument().getItems())); distributedAccounts = purapAccountingService.generateAccountDistributionForProration( summaryAccounts, totalAmount, new Integer("6"), InvoiceAccount.class); } } if (CollectionUtils.isNotEmpty(distributedAccounts) && CollectionUtils.isEmpty(item.getSourceAccountingLines())) { item.setSourceAccountingLines(distributedAccounts); } } // update the item purapAccountingService.updateItemAccountAmounts(item); } // 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(invoiceDocument); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#addHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, * String) */ @Override public InvoiceDocument addHoldOnInvoice(InvoiceDocument document, String note) throws Exception { // save the note Note noteObj = documentService.createNoteFromDocument(document, note); document.addNote(noteObj); noteService.save(noteObj); // MSU Contribution OLEMI-8456 DT 3822 OLECNTRB-959 document.setHoldIndicator(true); document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId()); purapService.saveDocumentNoValidation(document); return document; } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#removeHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, String) */ @Override public InvoiceDocument removeHoldOnInvoice(InvoiceDocument document, String note) throws Exception { // save the note Note noteObj = documentService.createNoteFromDocument(document, note); document.addNote(noteObj); noteService.save(noteObj); // MSU Contribution OLEMI-8456 DT 3822 OLECNTRB-959 document.setHoldIndicator(false); document.setLastActionPerformedByPersonId(null); purapService.saveDocumentNoValidation(document); return document; } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#addHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, * String) */ @Override public void requestCancelOnInvoice(InvoiceDocument document, String note) throws Exception { // save the note Note noteObj = documentService.createNoteFromDocument(document, note); document.addNote(noteObj); noteService.save(noteObj); // MSU Contribution OLEMI-8456 DT 3822 OLECNTRB-959 document.setInvoiceCancelIndicator(true); document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId()); document.setAccountsPayableRequestCancelIdentifier( GlobalVariables.getUserSession().getPerson().getPrincipalId()); purapService.saveDocumentNoValidation(document); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#removeHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, String) */ @Override public void removeRequestCancelOnInvoice(InvoiceDocument 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(InvoiceDocument document) { document.setInvoiceCancelIndicator(false); document.setLastActionPerformedByPersonId(null); document.setAccountsPayableRequestCancelIdentifier(null); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#isExtracted(org.kuali.ole.module.purap.document.InvoiceDocument) */ @Override public boolean isExtracted(InvoiceDocument document) { return (ObjectUtils.isNull(document.getExtractedTimestamp()) ? false : true); } protected boolean isBeingAdHocRouted(InvoiceDocument document) { return financialSystemWorkflowHelperService.isAdhocApprovalRequestedForPrincipal( document.getDocumentHeader().getWorkflowDocument(), GlobalVariables.getUserSession().getPrincipalId()); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#cancelExtractedInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, * String) */ @Override public void cancelExtractedInvoice(InvoiceDocument invoice, String note) { LOG.debug("cancelExtractedInvoice() started"); if (InvoiceStatuses.CANCELLED_STATUSES.contains(invoice.getApplicationDocumentStatus())) { LOG.debug("cancelExtractedInvoice() ended"); return; } try { Note cancelNote = documentService.createNoteFromDocument(invoice, note); invoice.addNote(cancelNote); noteService.save(cancelNote); } catch (Exception e) { throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE, e); } // cancel extracted should not reopen PO invoice.setReopenPurchaseOrderIndicator(false); getAccountsPayableService().cancelAccountsPayableDocument(invoice, ""); // Performs save, so // no explicit save // is necessary if (LOG.isDebugEnabled()) { LOG.debug("cancelExtractedInvoice() PRQS " + invoice.getPurapDocumentIdentifier() + " Cancelled Without Workflow"); LOG.debug("cancelExtractedInvoice() ended"); } } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#resetExtractedInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, * String) */ @Override public void resetExtractedInvoice(InvoiceDocument invoice, String note) { LOG.debug("resetExtractedInvoice() started"); if (InvoiceStatuses.CANCELLED_STATUSES.contains(invoice.getApplicationDocumentStatus())) { LOG.debug("resetExtractedInvoice() ended"); return; } invoice.setExtractedTimestamp(null); invoice.setPaymentPaidTimestamp(null); String noteText = "This Invoice is being reset for extraction by PDP " + note; try { Note resetNote = documentService.createNoteFromDocument(invoice, noteText); invoice.addNote(resetNote); noteService.save(resetNote); } catch (Exception e) { throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e); } purapService.saveDocumentNoValidation(invoice); if (LOG.isDebugEnabled()) { LOG.debug("resetExtractedInvoice() PRQS " + invoice.getPurapDocumentIdentifier() + " Reset from Extracted status"); } } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#populateInvoice(org.kuali.ole.module.purap.document.InvoiceDocument) */ /*@Override public void populateInvoice(InvoiceDocument invoiceDocument) { PurchaseOrderDocument purchaseOrderDocument = invoiceDocument.getPurchaseOrderDocument(); // make a call to search for expired/closed accounts HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = getAccountsPayableService().getExpiredOrClosedAccountList(invoiceDocument); invoiceDocument.populateInvoiceFromPurchaseOrders(invoiceDocument, expiredOrClosedAccountList); // invoiceDocument.getDocumentHeader().setDocumentDescription(createPreqDocumentDescription(invoiceDocument.getPurchaseOrderIdentifier(), invoiceDocument.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(invoiceDocument, expiredOrClosedAccountList); // set indicator so a message is displayed for accounts that were replaced due to expired/closed status if (!expiredOrClosedAccountList.isEmpty()) { invoiceDocument.setContinuationAccountIndicator(true); } // add discount item calculateDiscount(invoiceDocument); // distribute accounts (i.e. proration) distributeAccounting(invoiceDocument); // set bank code to default bank code in the system parameter Bank defaultBank = bankService.getDefaultBankByDocType(invoiceDocument.getClass()); if (defaultBank != null) { invoiceDocument.setBankCode(defaultBank.getBankCode()); invoiceDocument.setBank(defaultBank); } }*/ /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#createPreqDocumentDescription(Integer, * String) */ @Override 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.ole.module.purap.document.service.InvoiceService#populateAndSaveInvoice(org.kuali.ole.module.purap.document.InvoiceDocument) */ @Override public void populateAndSaveInvoice(InvoiceDocument prqs) throws WorkflowException { try { prqs.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_IN_PROCESS); documentService.saveDocument(prqs, AttributedContinuePurapEvent.class); } catch (ValidationException ve) { prqs.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_INITIATE); } catch (WorkflowException we) { prqs.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_INITIATE); String errorMsg = "Error saving document # " + prqs.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.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#shouldPurchaseOrderBeReversed * (org.kuali.ole.module.purap.document.AccountsPayableDocument) */ @Override public boolean shouldPurchaseOrderBeReversed(AccountsPayableDocument apDoc) { PurchaseOrderDocument po = apDoc.getPurchaseOrderDocument(); if (ObjectUtils.isNull(po)) { throw new RuntimeException("po should never be null on PRQS"); } // 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.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#getPersonForCancel(org.kuali.ole.module.purap.document.AccountsPayableDocument) */ @Override public Person getPersonForCancel(AccountsPayableDocument apDoc) { InvoiceDocument prqsDoc = (InvoiceDocument) apDoc; Person user = null; if (prqsDoc.isInvoiceCancelIndicator()) { user = prqsDoc.getLastActionPerformedByUser(); } return user; } /** * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#takePurchaseOrderCancelAction(org.kuali.ole.module.purap.document.AccountsPayableDocument) */ @Override public void takePurchaseOrderCancelAction(AccountsPayableDocument apDoc) { InvoiceDocument prqsDocument = (InvoiceDocument) apDoc; if (prqsDocument.isReopenPurchaseOrderIndicator()) { String docType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT; purchaseOrderService.createAndRoutePotentialChangeDocument( prqsDocument.getPurchaseOrderDocument().getDocumentNumber(), docType, "reopened by Credit Memo " + apDoc.getPurapDocumentIdentifier() + "cancel", new ArrayList(), PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_REOPEN); } } /** * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#updateStatusByNode(String, * org.kuali.ole.module.purap.document.AccountsPayableDocument) */ @Override public String updateStatusByNode(String currentNodeName, AccountsPayableDocument apDoc) { return updateStatusByNode(currentNodeName, (InvoiceDocument) apDoc); } /** * Updates the status of the payment request document. * * @param currentNodeName The current node name. * @param prqsDoc The payment request document whose status to be updated. * @return The canceled status code. */ protected String updateStatusByNode(String currentNodeName, InvoiceDocument prqsDoc) { // remove request cancel if necessary clearRequestCancelFields(prqsDoc); // update the status on the document String cancelledStatus = ""; if (StringUtils.isEmpty(currentNodeName)) { // if empty probably not coming from workflow cancelledStatus = InvoiceStatuses.APPDOC_CANCELLED_POST_AP_APPROVE; } else { cancelledStatus = InvoiceStatuses.getInvoiceAppDocDisapproveStatuses().get(currentNodeName); } if (StringUtils.isNotBlank(cancelledStatus)) { try { prqsDoc.updateAndSaveAppDocStatus(cancelledStatus); } catch (WorkflowException we) { throw new RuntimeException( "Unable to save the route status data for document: " + prqsDoc.getDocumentNumber(), we); } purapService.saveDocumentNoValidation(prqsDoc); } else { logAndThrowRuntimeException( "No status found to set for document being disapproved in node '" + currentNodeName + "'"); } return cancelledStatus; } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#markPaid(org.kuali.ole.module.purap.document.InvoiceDocument, * java.sql.Date) */ @Override public void markPaid(InvoiceDocument pr, Date processDate) { LOG.debug("markPaid() started"); pr.setPaymentPaidTimestamp(new Timestamp(processDate.getTime())); purapService.saveDocumentNoValidation(pr); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#hasDiscountItem(org.kuali.ole.module.purap.document.InvoiceDocument) */ @Override public boolean hasDiscountItem(InvoiceDocument prqs) { return ObjectUtils.isNotNull(findDiscountItem(prqs)); } /** * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#poItemEligibleForAp(org.kuali.ole.module.purap.document.AccountsPayableDocument, * org.kuali.ole.module.purap.businessobject.PurchaseOrderItem) */ @Override 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 public void removeIneligibleAdditionalCharges(InvoiceDocument document) { List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>(); for (InvoiceItem item : (List<InvoiceItem>) 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(), 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 (InvoiceItem item : itemsToRemove) { document.getItems().remove(item); } } @Override public void changeVendor(InvoiceDocument prqs, Integer headerId, Integer detailId) { VendorDetail primaryVendor = vendorService.getVendorDetail( prqs.getOriginalVendorHeaderGeneratedIdentifier(), prqs.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"); } prqs.setVendorDetail(vd); prqs.setVendorName(vd.getVendorName()); prqs.setVendorNumber(vd.getVendorNumber()); prqs.setVendorHeaderGeneratedIdentifier(vd.getVendorHeaderGeneratedIdentifier()); prqs.setVendorDetailAssignedIdentifier(vd.getVendorDetailAssignedIdentifier()); prqs.setVendorPaymentTermsCode(vd.getVendorPaymentTermsCode()); prqs.setVendorShippingPaymentTermsCode(vd.getVendorShippingPaymentTermsCode()); prqs.setVendorShippingTitleCode(vd.getVendorShippingTitleCode()); prqs.refreshReferenceObject("vendorPaymentTerms"); prqs.refreshReferenceObject("vendorShippingPaymentTerms"); // Set vendor address String deliveryCampus = prqs.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 (prqs != null) { setVendorAddress(va, prqs); } else { LOG.error("changeVendor(): Null link back to the Purchase Order."); throw new PurError("Null link back to the Purchase Order."); } // change document description // prqs.getDocumentHeader().setDocumentDescription(createPreqDocumentDescription(prqs.getPurchaseOrderIdentifier(), prqs.getVendorName())); } /** * Set the Vendor address of the given ID. * * @param va ID of the address to set * @param inv Invoice to set in * @return New Invoice to use */ protected void setVendorAddress(VendorAddress va, InvoiceDocument inv) { if (va != null) { inv.setVendorAddressGeneratedIdentifier(va.getVendorAddressGeneratedIdentifier()); inv.setVendorAddressInternationalProvinceName(va.getVendorAddressInternationalProvinceName()); inv.setVendorLine1Address(va.getVendorLine1Address()); inv.setVendorLine2Address(va.getVendorLine2Address()); inv.setVendorCityName(va.getVendorCityName()); inv.setVendorStateCode(va.getVendorStateCode()); inv.setVendorPostalCode(va.getVendorZipCode()); inv.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 Invoice. * * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#generateGLEntriesCreateAccountsPayableDocument(org.kuali.ole.module.purap.document.AccountsPayableDocument) */ @Override public void generateGLEntriesCreateAccountsPayableDocument(AccountsPayableDocument apDocument) { // if(apDocument.) InvoiceDocument invoice = (InvoiceDocument) apDocument; //System.out.println(" generateGLEntriesCreateAccountsPayableDocument apDocument" + ((InvoiceDocument) apDocument).getDocumentType()); // JHK: this is not being injected because it would cause a circular reference in the Spring definitions SpringContext.getBean(PurapGeneralLedgerService.class).generateEntriesCreateInvoice(invoice); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#hasActiveInvoicesForPurchaseOrder(Integer) */ @Override public boolean hasActiveInvoicesForPurchaseOrder(Integer purchaseOrderIdentifier) { boolean hasActivePreqs = false; List<String> docNumbers = null; WorkflowDocument workflowDocument = null; docNumbers = invoiceDao.getActiveInvoiceDocumentNumbersForPurchaseOrder(purchaseOrderIdentifier); docNumbers = filterInvoiceByAppDocStatus(docNumbers, InvoiceStatuses.STATUSES_POTENTIALLY_ACTIVE); for (String docNumber : docNumbers) { try { workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, 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; } /** * Since Invoice does not have the app doc status, perform an additional lookup * through doc search by using list of Invoice Doc numbers. Query appDocStatus * from workflow document and filter against the provided status * <p/> * DocumentSearch allows for multiple docNumber lookup by docId|docId|docId conversion * * @param lookupDocNumbers * @param appDocStatus * @return */ protected List<String> filterInvoiceByAppDocStatus(List<String> lookupDocNumbers, String... appDocStatus) { boolean valid = false; final String DOC_NUM_DELIM = "|"; StrBuilder routerHeaderIdBuilder = new StrBuilder().appendWithSeparators(lookupDocNumbers, DOC_NUM_DELIM); List<String> invoiceDocNumbers = new ArrayList<String>(); DocumentSearchCriteria.Builder documentSearchCriteriaDTO = DocumentSearchCriteria.Builder.create(); documentSearchCriteriaDTO.setDocumentId(routerHeaderIdBuilder.toString()); documentSearchCriteriaDTO.setDocumentTypeName(PurapConstants.PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT); documentSearchCriteriaDTO.setApplicationDocumentStatuses(Arrays.asList(appDocStatus)); DocumentSearchResults reqDocumentsList = KewApiServiceLocator.getWorkflowDocumentService() .documentSearch("", documentSearchCriteriaDTO.build()); for (DocumentSearchResult reqDocument : reqDocumentsList.getSearchResults()) { ///use the appDocStatus from the KeyValueDTO result to look up custom status if (Arrays.asList(appDocStatus).contains(reqDocument.getDocument().getApplicationDocumentStatus())) { //found the matching status, retrieve the routeHeaderId and add to the list invoiceDocNumbers.add(reqDocument.getDocument().getDocumentId()); } } return invoiceDocNumbers; } /** * Wrapper class to the filterInvoiceByAppDocStatus * <p/> * This class first extract the payment request document numbers from the Invoice Collections, * then perform the filterInvoiceByAppDocStatus function. Base on the filtered payment request * doc number, reconstruct the filtered Invoice Collection * * @param invoiceDocuments * @param appDocStatus * @return */ protected Collection<InvoiceDocument> filterInvoiceByAppDocStatus(Collection<InvoiceDocument> invoiceDocuments, String... appDocStatus) { List<String> invoiceDocNumbers = new ArrayList<String>(); for (InvoiceDocument invoice : invoiceDocuments) { invoiceDocNumbers.add(invoice.getDocumentNumber()); } List<String> filteredInvoiceDocNumbers = filterInvoiceByAppDocStatus(invoiceDocNumbers, appDocStatus); Collection<InvoiceDocument> filteredInvoiceDocuments = new ArrayList<InvoiceDocument>(); //add to filtered collection if it is in the filtered payment request doc number list for (InvoiceDocument invoice : invoiceDocuments) { if (filteredInvoiceDocNumbers.contains(invoice.getDocumentNumber())) { filteredInvoiceDocuments.add(invoice); } } return filteredInvoiceDocuments; } /** * Wrapper class to the filterInvoiceByAppDocStatus (Collection<InvoiceDocument>) * <p/> * This class first construct the Invoice Collection from the iterator, and then process through * filterInvoiceByAppDocStatus * * @param invoiceIterator * @param appDocStatus * @return */ protected Iterator<InvoiceDocument> filterInvoiceByAppDocStatus(Iterator<InvoiceDocument> invoiceIterator, String... appDocStatus) { Collection<InvoiceDocument> invoiceDocuments = new ArrayList<InvoiceDocument>(); for (; invoiceIterator.hasNext();) { invoiceDocuments.add(invoiceIterator.next()); } return filterInvoiceByAppDocStatus(invoiceDocuments, appDocStatus).iterator(); } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#processInvoiceInReceivingStatus() */ @Override public void processInvoiceInReceivingStatus() { List<String> docNumbers = invoiceDao.getInvoiceInReceivingStatus(); docNumbers = filterInvoiceByAppDocStatus(docNumbers, InvoiceStatuses.APPDOC_AWAITING_RECEIVING_REVIEW); List<InvoiceDocument> prqssAwaitingReceiving = new ArrayList<InvoiceDocument>(); for (String docNumber : docNumbers) { InvoiceDocument prqs = getInvoiceByDocumentNumber(docNumber); if (ObjectUtils.isNotNull(prqs)) { prqssAwaitingReceiving.add(prqs); } } if (ObjectUtils.isNotNull(prqssAwaitingReceiving)) { for (InvoiceDocument prqsDoc : prqssAwaitingReceiving) { if (prqsDoc.isReceivingRequirementMet()) { try { documentService.approveDocument(prqsDoc, "Approved by Receiving Required PRQS job", null); } catch (WorkflowException e) { LOG.error( "processInvoiceInReceivingStatus() Error approving payment request document from awaiting receiving", e); throw new RuntimeException( "Error approving payment request document from awaiting receiving", e); } } } } } /** * @see org.kuali.ole.module.purap.document.service.InvoiceService#allowBackpost(org.kuali.ole.module.purap.document.InvoiceDocument) */ @Override public boolean allowBackpost(InvoiceDocument invoiceDocument) { int allowBackpost = (Integer .parseInt(getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD, 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 prqss allow backpost on the last day Calendar allowBackpostDate = Calendar.getInstance(); allowBackpostDate.setTime(priorClosingDate.getTime()); allowBackpostDate.add(Calendar.DATE, allowBackpost + 1); Calendar prqsInvoiceDate = Calendar.getInstance(); prqsInvoiceDate.setTime( invoiceDocument.getInvoiceDate() != null ? invoiceDocument.getInvoiceDate() : new java.util.Date()); // 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) && (prqsInvoiceDate.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 public boolean isPurchaseOrderValidForInvoiceDocumentCreation(InvoiceDocument invoiceDocument, PurchaseOrderDocument po) { Integer POID = invoiceDocument.getPurchaseOrderIdentifier(); boolean valid = true; PurchaseOrderDocument purchaseOrderDocument = invoiceDocument.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 Invoice for it } else { // Verify that there exists at least 1 item left to be invoiced // valid &= encumberedItemExistsForInvoicing(purchaseOrderDocument); } return valid; } @Override public boolean encumberedItemExistsForInvoicing(PurchaseOrderDocument document) { boolean zeroDollar = true; GlobalVariables.getMessageMap().clearErrorPath(); GlobalVariables.getMessageMap().addToErrorPath(OLEPropertyConstants.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; } public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } public void setNoteService(NoteService noteService) { this.noteService = noteService; } public void setPurapService(PurapService purapService) { this.purapService = purapService; } public void setInvoiceDao(InvoiceDao invoiceDao) { this.invoiceDao = invoiceDao; } public void setNegativeInvoiceApprovalLimitService( NegativeInvoiceApprovalLimitService negativeInvoiceApprovalLimitService) { this.negativeInvoiceApprovalLimitService = negativeInvoiceApprovalLimitService; } public void setPurapAccountingService(PurapAccountingService purapAccountingService) { this.purapAccountingService = purapAccountingService; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } public void setPurapWorkflowIntegrationService( PurApWorkflowIntegrationService purapWorkflowIntegrationService) { this.purapWorkflowIntegrationService = purapWorkflowIntegrationService; } public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) { this.workflowDocumentService = workflowDocumentService; } public void setAccountsPayableService(AccountsPayableService accountsPayableService) { this.accountsPayableService = accountsPayableService; } public void setVendorService(VendorService vendorService) { this.vendorService = vendorService; } public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { this.dataDictionaryService = dataDictionaryService; } public void setUniversityDateService(UniversityDateService universityDateService) { this.universityDateService = universityDateService; } public void setBankService(BankService bankService) { this.bankService = bankService; } public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) { this.purchaseOrderService = purchaseOrderService; } public void setFinancialSystemWorkflowHelperService( FinancialSystemWorkflowHelperService financialSystemWorkflowHelperService) { this.financialSystemWorkflowHelperService = financialSystemWorkflowHelperService; } public void setKualiRuleService(KualiRuleService kualiRuleService) { this.kualiRuleService = kualiRuleService; } /** * Gets the accountsPayableService attribute. * * @return Returns the accountsPayableService */ public AccountsPayableService getAccountsPayableService() { return SpringContext.getBean(AccountsPayableService.class); } public String getParameter(String namespaceCode, String componentCode, String parameterName) { ParameterKey parameterKey = ParameterKey.create(OLEConstants.OLE_CMPNT, namespaceCode, componentCode, parameterName); Parameter parameter = CoreServiceApiServiceLocator.getParameterRepositoryService() .getParameter(parameterKey); return parameter != null ? parameter.getValue() : null; } public boolean getParameterBoolean(String namespaceCode, String componentCode, String parameterName) { ParameterKey parameterKey = ParameterKey.create(OLEConstants.OLE_CMPNT, namespaceCode, componentCode, parameterName); Parameter parameter = CoreServiceApiServiceLocator.getParameterRepositoryService() .getParameter(parameterKey); if (parameter != null && parameter.getValue().equalsIgnoreCase("y")) { return true; } return false; } }