0001: /*
0002: * Copyright 2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.module.purap.service.impl;
0017:
0018: import java.math.BigDecimal;
0019: import java.sql.Date;
0020: import java.sql.Timestamp;
0021: import java.util.ArrayList;
0022: import java.util.Calendar;
0023: import java.util.Collection;
0024: import java.util.HashMap;
0025: import java.util.Iterator;
0026: import java.util.List;
0027: import java.util.Map;
0028:
0029: import org.apache.commons.collections.CollectionUtils;
0030: import org.apache.commons.lang.StringUtils;
0031: import org.kuali.core.bo.DocumentHeader;
0032: import org.kuali.core.bo.Note;
0033: import org.kuali.core.bo.user.UniversalUser;
0034: import org.kuali.core.exceptions.ValidationException;
0035: import org.kuali.core.service.BusinessObjectService;
0036: import org.kuali.core.service.DataDictionaryService;
0037: import org.kuali.core.service.DateTimeService;
0038: import org.kuali.core.service.DocumentService;
0039: import org.kuali.core.service.KualiConfigurationService;
0040: import org.kuali.core.service.NoteService;
0041: import org.kuali.core.util.GlobalVariables;
0042: import org.kuali.core.util.KualiDecimal;
0043: import org.kuali.core.util.ObjectUtils;
0044: import org.kuali.kfs.KFSPropertyConstants;
0045: import org.kuali.kfs.bo.SourceAccountingLine;
0046: import org.kuali.kfs.context.SpringContext;
0047: import org.kuali.kfs.rule.event.DocumentSystemSaveEvent;
0048: import org.kuali.kfs.service.ParameterService;
0049: import org.kuali.kfs.service.impl.ParameterConstants;
0050: import org.kuali.module.purap.PurapConstants;
0051: import org.kuali.module.purap.PurapKeyConstants;
0052: import org.kuali.module.purap.PurapParameterConstants;
0053: import org.kuali.module.purap.PurapWorkflowConstants;
0054: import org.kuali.module.purap.PurapConstants.PREQDocumentsStrings;
0055: import org.kuali.module.purap.PurapConstants.PaymentRequestStatuses;
0056: import org.kuali.module.purap.PurapWorkflowConstants.NodeDetails;
0057: import org.kuali.module.purap.PurapWorkflowConstants.PaymentRequestDocument.NodeDetailEnum;
0058: import org.kuali.module.purap.bo.AutoApproveExclude;
0059: import org.kuali.module.purap.bo.ItemType;
0060: import org.kuali.module.purap.bo.NegativePaymentRequestApprovalLimit;
0061: import org.kuali.module.purap.bo.PaymentRequestAccount;
0062: import org.kuali.module.purap.bo.PaymentRequestItem;
0063: import org.kuali.module.purap.bo.PurApAccountingLine;
0064: import org.kuali.module.purap.bo.PurchaseOrderItem;
0065: import org.kuali.module.purap.dao.PaymentRequestDao;
0066: import org.kuali.module.purap.document.AccountsPayableDocument;
0067: import org.kuali.module.purap.document.CreditMemoDocument;
0068: import org.kuali.module.purap.document.PaymentRequestDocument;
0069: import org.kuali.module.purap.document.PurchaseOrderDocument;
0070: import org.kuali.module.purap.rule.event.ContinueAccountsPayableEvent;
0071: import org.kuali.module.purap.service.AccountsPayableService;
0072: import org.kuali.module.purap.service.NegativePaymentRequestApprovalLimitService;
0073: import org.kuali.module.purap.service.PaymentRequestService;
0074: import org.kuali.module.purap.service.PurApWorkflowIntegrationService;
0075: import org.kuali.module.purap.service.PurapAccountingService;
0076: import org.kuali.module.purap.service.PurapService;
0077: import org.kuali.module.purap.service.PurchaseOrderService;
0078: import org.kuali.module.purap.util.ExpiredOrClosedAccountEntry;
0079: import org.kuali.module.purap.util.PurApItemUtils;
0080: import org.kuali.module.purap.util.VendorGroupingHelper;
0081: import org.kuali.module.vendor.bo.PaymentTermType;
0082: import org.springframework.transaction.annotation.Transactional;
0083:
0084: import edu.iu.uis.eden.exception.WorkflowException;
0085:
0086: /**
0087: * This class provides services of use to a payment request document
0088: */
0089: @Transactional
0090: public class PaymentRequestServiceImpl implements PaymentRequestService {
0091: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0092: .getLogger(PaymentRequestServiceImpl.class);
0093:
0094: private DateTimeService dateTimeService;
0095: private DocumentService documentService;
0096: private NoteService noteService;
0097: private PurapService purapService;
0098: private PaymentRequestDao paymentRequestDao;
0099: private PurchaseOrderService purchaseOrderService;
0100: private ParameterService parameterService;
0101: private KualiConfigurationService configurationService;
0102: private NegativePaymentRequestApprovalLimitService negativePaymentRequestApprovalLimitService;
0103: private PurapAccountingService purapAccountingService;
0104: private BusinessObjectService businessObjectService;
0105: private PurApWorkflowIntegrationService purapWorkflowIntegrationService;
0106:
0107: public void setDateTimeService(DateTimeService dateTimeService) {
0108: this .dateTimeService = dateTimeService;
0109: }
0110:
0111: public void setParameterService(ParameterService parameterService) {
0112: this .parameterService = parameterService;
0113: }
0114:
0115: public void setConfigurationService(
0116: KualiConfigurationService configurationService) {
0117: this .configurationService = configurationService;
0118: }
0119:
0120: public void setDocumentService(DocumentService documentService) {
0121: this .documentService = documentService;
0122: }
0123:
0124: public void setNoteService(NoteService noteService) {
0125: this .noteService = noteService;
0126: }
0127:
0128: public void setPurapService(PurapService purapService) {
0129: this .purapService = purapService;
0130: }
0131:
0132: public void setPaymentRequestDao(PaymentRequestDao paymentRequestDao) {
0133: this .paymentRequestDao = paymentRequestDao;
0134: }
0135:
0136: public void setPurchaseOrderService(
0137: PurchaseOrderService purchaseOrderService) {
0138: this .purchaseOrderService = purchaseOrderService;
0139: }
0140:
0141: public void setNegativePaymentRequestApprovalLimitService(
0142: NegativePaymentRequestApprovalLimitService negativePaymentRequestApprovalLimitService) {
0143: this .negativePaymentRequestApprovalLimitService = negativePaymentRequestApprovalLimitService;
0144: }
0145:
0146: public void setPurapAccountingService(
0147: PurapAccountingService purapAccountingService) {
0148: this .purapAccountingService = purapAccountingService;
0149: }
0150:
0151: public void setBusinessObjectService(
0152: BusinessObjectService businessObjectService) {
0153: this .businessObjectService = businessObjectService;
0154: }
0155:
0156: public void setPurapWorkflowIntegrationService(
0157: PurApWorkflowIntegrationService purapWorkflowIntegrationService) {
0158: this .purapWorkflowIntegrationService = purapWorkflowIntegrationService;
0159: }
0160:
0161: /**
0162: * @see org.kuali.module.purap.server.PaymentRequestService.getPaymentRequestsToExtractByCM()
0163: */
0164: public Iterator<PaymentRequestDocument> getPaymentRequestsToExtractByCM(
0165: String campusCode, CreditMemoDocument cmd) {
0166: LOG.debug("getPaymentRequestsByCM() started");
0167:
0168: return paymentRequestDao.getPaymentRequestsToExtract(
0169: campusCode, null, null, cmd
0170: .getVendorHeaderGeneratedIdentifier(), cmd
0171: .getVendorDetailAssignedIdentifier());
0172: }
0173:
0174: public Iterator<PaymentRequestDocument> getPaymentRequestsToExtractByVendor(
0175: String campusCode, VendorGroupingHelper vendor) {
0176: LOG.debug("getPaymentRequestsByVendor() started");
0177:
0178: return paymentRequestDao.getPaymentRequestsToExtractForVendor(
0179: campusCode, vendor);
0180: }
0181:
0182: /**
0183: * @see org.kuali.module.purap.server.PaymentRequestService.getPaymentRequestsToExtract()
0184: */
0185: public Iterator<PaymentRequestDocument> getPaymentRequestsToExtract() {
0186: LOG.debug("getPaymentRequestsToExtract() started");
0187:
0188: return paymentRequestDao.getPaymentRequestsToExtract(false,
0189: null);
0190: }
0191:
0192: /**
0193: * @see org.kuali.module.purap.service.PaymentRequestService#getPaymentRequestsToExtractSpecialPayments(java.lang.String)
0194: */
0195: public Iterator<PaymentRequestDocument> getPaymentRequestsToExtractSpecialPayments(
0196: String chartCode) {
0197: LOG
0198: .debug("getPaymentRequestsToExtractSpecialPayments() started");
0199:
0200: return paymentRequestDao.getPaymentRequestsToExtract(true,
0201: chartCode);
0202: }
0203:
0204: /**
0205: * @see org.kuali.module.purap.service.PaymentRequestService#getImmediatePaymentRequestsToExtract(java.lang.String)
0206: */
0207: public Iterator<PaymentRequestDocument> getImmediatePaymentRequestsToExtract(
0208: String chartCode) {
0209: LOG.debug("getImmediatePaymentRequestsToExtract() started");
0210:
0211: return paymentRequestDao
0212: .getImmediatePaymentRequestsToExtract(chartCode);
0213: }
0214:
0215: /**
0216: * @see org.kuali.module.purap.service.PaymentRequestService#getPaymentRequestToExtractByChart(java.lang.String)
0217: */
0218: public Iterator<PaymentRequestDocument> getPaymentRequestToExtractByChart(
0219: String chartCode) {
0220: LOG.debug("getPaymentRequestToExtractByChart() started");
0221:
0222: return paymentRequestDao.getPaymentRequestsToExtract(false,
0223: chartCode);
0224: }
0225:
0226: /**
0227: * @see org.kuali.module.purap.service.PaymentRequestService.autoApprovePaymentRequests()
0228: */
0229: public boolean autoApprovePaymentRequests() {
0230: boolean hadErrorAtLeastOneError = true;
0231: // should objects from existing user session be copied over
0232: List<PaymentRequestDocument> docs = paymentRequestDao
0233: .getEligibleForAutoApproval();
0234: if (docs != null) {
0235: String samt = parameterService
0236: .getParameterValue(
0237: PaymentRequestDocument.class,
0238: PurapParameterConstants.PURAP_DEFAULT_NEGATIVE_PAYMENT_REQUEST_APPROVAL_LIMIT);
0239: KualiDecimal defaultMinimumLimit = new KualiDecimal(samt);
0240: for (PaymentRequestDocument paymentRequestDocument : docs) {
0241: hadErrorAtLeastOneError |= !autoApprovePaymentRequest(
0242: paymentRequestDocument, defaultMinimumLimit);
0243: }
0244: }
0245: return hadErrorAtLeastOneError;
0246: }
0247:
0248: /**
0249: * @see org.kuali.module.purap.service.PaymentRequestService#autoApprovePaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument, org.kuali.core.util.KualiDecimal)
0250: */
0251: public boolean autoApprovePaymentRequest(
0252: PaymentRequestDocument doc, KualiDecimal defaultMinimumLimit) {
0253: if (isEligibleForAutoApproval(doc, defaultMinimumLimit)) {
0254: try {
0255: purapService.updateStatus(doc,
0256: PaymentRequestStatuses.AUTO_APPROVED);
0257: documentService.blanketApproveDocument(doc,
0258: "auto-approving: Total is below threshold.",
0259: null);
0260: } catch (WorkflowException we) {
0261: LOG.error(
0262: "Exception encountered when approving document number "
0263: + doc.getDocumentNumber() + ".", we);
0264: return false;
0265: }
0266: }
0267: return true;
0268: }
0269:
0270: /**
0271: * Determines whether or not a payment request document can be automatically approved. FYI - If fiscal reviewers are
0272: * allowed to save account changes without the full account validation running then this method must call full account
0273: * validation to make sure auto approver is not blanket approving an invalid document according the the accounts on the items
0274: *
0275: * @param document The payment request document to be determined whether it can be automatically approved.
0276: * @param defaultMinimumLimit The amount to be used as the minimum amount if no limit was found or the default is
0277: * less than the limit.
0278: * @return boolean true if the payment request document is eligible for auto approval.
0279: */
0280: private boolean isEligibleForAutoApproval(
0281: PaymentRequestDocument document,
0282: KualiDecimal defaultMinimumLimit) {
0283: // check to make sure the payment request isn't scheduled to stop in tax review.
0284: if (purapWorkflowIntegrationService
0285: .willDocumentStopAtGivenFutureRouteNode(
0286: document,
0287: PurapWorkflowConstants.PaymentRequestDocument.NodeDetailEnum.VENDOR_TAX_REVIEW)) {
0288: return false;
0289: }
0290:
0291: // This minimum will be set to the minimum limit derived from all
0292: // accounting lines on the document. If no limit is determined, the
0293: // default will be used.
0294: KualiDecimal minimumAmount = null;
0295:
0296: // Iterate all source accounting lines on the document, deriving a
0297: // minimum limit from each according to chart, chart and account, and
0298: // chart and organization.
0299: for (SourceAccountingLine line : purapAccountingService
0300: .generateSummary(document.getItems())) {
0301: // check to make sure the account is in the auto approve exclusion list
0302: Map<String, String> autoApproveMap = new HashMap<String, String>();
0303: autoApproveMap.put("chartOfAccountsCode", line
0304: .getChartOfAccountsCode());
0305: autoApproveMap
0306: .put("accountNumber", line.getAccountNumber());
0307: AutoApproveExclude autoApproveExclude = (AutoApproveExclude) businessObjectService
0308: .findByPrimaryKey(AutoApproveExclude.class,
0309: autoApproveMap);
0310: if (autoApproveExclude != null) {
0311: return false;
0312: }
0313:
0314: minimumAmount = getMinimumLimitAmount(
0315: negativePaymentRequestApprovalLimitService
0316: .findByChart(line.getChartOfAccountsCode()),
0317: minimumAmount);
0318: minimumAmount = getMinimumLimitAmount(
0319: negativePaymentRequestApprovalLimitService
0320: .findByChartAndAccount(line
0321: .getChartOfAccountsCode(), line
0322: .getAccountNumber()), minimumAmount);
0323: minimumAmount = getMinimumLimitAmount(
0324: negativePaymentRequestApprovalLimitService
0325: .findByChartAndOrganization(line
0326: .getChartOfAccountsCode(), line
0327: .getOrganizationReferenceId()),
0328: minimumAmount);
0329: }
0330:
0331: // If no limit was found or the default is less than the limit, the default limit is used.
0332: if (ObjectUtils.isNull(minimumAmount)
0333: || defaultMinimumLimit.compareTo(minimumAmount) < 0) {
0334: minimumAmount = defaultMinimumLimit;
0335: }
0336:
0337: // The document is eligible for auto-approval if the document total is below the limit.
0338: if (document.getDocumentHeader()
0339: .getFinancialDocumentTotalAmount().isLessThan(
0340: minimumAmount)) {
0341: return true;
0342: }
0343:
0344: return false;
0345: }
0346:
0347: /**
0348: * This method iterates a collection of negative payment request approval limits and returns the minimum of a given minimum
0349: * amount and the least among the limits in the collection.
0350: *
0351: * @param limits The collection of NegativePaymentRequestApprovalLimit to be used in determining the minimum limit amount.
0352: * @param minimumAmount The amount to be compared with the collection of NegativePaymentRequestApprovalLimit to determine the
0353: * minimum limit amount.
0354: * @return The minimum of the given minimum amount and the least among the limits in the collection.
0355: */
0356: private KualiDecimal getMinimumLimitAmount(
0357: Collection<NegativePaymentRequestApprovalLimit> limits,
0358: KualiDecimal minimumAmount) {
0359: for (NegativePaymentRequestApprovalLimit limit : limits) {
0360: KualiDecimal amount = limit
0361: .getNegativePaymentRequestApprovalLimitAmount();
0362: if (null == minimumAmount) {
0363: minimumAmount = amount;
0364: } else if (minimumAmount.isGreaterThan(amount)) {
0365: minimumAmount = amount;
0366: }
0367: }
0368: return minimumAmount;
0369: }
0370:
0371: /**
0372: * Retrieves a list of payment request documents with the given vendor id and invoice number.
0373: *
0374: * @param vendorHeaderGeneratedId The vendor header generated id.
0375: * @param vendorDetailAssignedId The vendor detail assigned id.
0376: * @param invoiceNumber The invoice number as entered by AP.
0377: * @return List of payment request document.
0378: */
0379: public List getPaymentRequestsByVendorNumberInvoiceNumber(
0380: Integer vendorHeaderGeneratedId,
0381: Integer vendorDetailAssignedId, String invoiceNumber) {
0382: LOG
0383: .debug("getActivePaymentRequestsByVendorNumberInvoiceNumber() started");
0384: return paymentRequestDao
0385: .getActivePaymentRequestsByVendorNumberInvoiceNumber(
0386: vendorHeaderGeneratedId,
0387: vendorDetailAssignedId, invoiceNumber);
0388: }
0389:
0390: /**
0391: * @see org.kuali.module.purap.service.PaymentRequestService#paymentRequestDuplicateMessages(org.kuali.module.purap.document.PaymentRequestDocument)
0392: */
0393: public HashMap<String, String> paymentRequestDuplicateMessages(
0394: PaymentRequestDocument document) {
0395: HashMap<String, String> msgs;
0396: msgs = new HashMap<String, String>();
0397:
0398: Integer purchaseOrderId = document.getPurchaseOrderIdentifier();
0399:
0400: if (ObjectUtils.isNotNull(document.getInvoiceDate())) {
0401: if (purapService.isDateAYearBeforeToday(document
0402: .getInvoiceDate())) {
0403: msgs
0404: .put(
0405: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0406: configurationService
0407: .getPropertyString(PurapKeyConstants.MESSAGE_INVOICE_DATE_A_YEAR_OR_MORE_PAST));
0408: }
0409: }
0410: PurchaseOrderDocument po = document.getPurchaseOrderDocument();
0411:
0412: if (po != null) {
0413: Integer vendorDetailAssignedId = po
0414: .getVendorDetailAssignedIdentifier();
0415: Integer vendorHeaderGeneratedId = po
0416: .getVendorHeaderGeneratedIdentifier();
0417:
0418: List<PaymentRequestDocument> preqs = getPaymentRequestsByVendorNumberInvoiceNumber(
0419: vendorHeaderGeneratedId, vendorDetailAssignedId,
0420: document.getInvoiceNumber());
0421:
0422: if (preqs.size() > 0) {
0423: boolean addedMessage = false;
0424: boolean foundCanceledPostApprove = false; // cancelled
0425: boolean foundCanceledPreApprove = false; // voided
0426: for (PaymentRequestDocument testPREQ : preqs) {
0427: if (StringUtils
0428: .equals(
0429: testPREQ.getStatusCode(),
0430: PaymentRequestStatuses.CANCELLED_POST_AP_APPROVE)) {
0431: foundCanceledPostApprove |= true;
0432: } else if (StringUtils
0433: .equals(
0434: testPREQ.getStatusCode(),
0435: PaymentRequestStatuses.CANCELLED_IN_PROCESS)) {
0436: foundCanceledPreApprove |= true;
0437: } else {
0438: msgs
0439: .put(
0440: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0441: configurationService
0442: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE));
0443: addedMessage = true;
0444: break;
0445: }
0446: }
0447: // Custom error message for duplicates related to cancelled/voided PREQs
0448: if (!addedMessage) {
0449: if (foundCanceledPostApprove
0450: && foundCanceledPreApprove) {
0451: msgs
0452: .put(
0453: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0454: configurationService
0455: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLEDORVOIDED));
0456: } else if (foundCanceledPreApprove) {
0457: msgs
0458: .put(
0459: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0460: configurationService
0461: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_VOIDED));
0462: } else if (foundCanceledPostApprove) {
0463: // messages.add("errors.duplicate.vendor.invoice.cancelled");
0464: msgs
0465: .put(
0466: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0467: configurationService
0468: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLED));
0469: }
0470: }
0471: }
0472:
0473: // Check that the invoice date and invoice total amount entered are not on any existing non-cancelled PREQs for this PO
0474: preqs = getPaymentRequestsByPOIdInvoiceAmountInvoiceDate(
0475: purchaseOrderId, document.getVendorInvoiceAmount(),
0476: document.getInvoiceDate());
0477: if (preqs.size() > 0) {
0478: boolean addedMessage = false;
0479: boolean foundCanceledPostApprove = false; // cancelled
0480: boolean foundCanceledPreApprove = false; // voided
0481: msgs
0482: .put(
0483: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0484: configurationService
0485: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT));
0486: for (PaymentRequestDocument testPREQ : preqs) {
0487: if (StringUtils
0488: .equalsIgnoreCase(
0489: testPREQ.getStatusCode(),
0490: PaymentRequestStatuses.CANCELLED_POST_AP_APPROVE)) {
0491: foundCanceledPostApprove |= true;
0492: } else if (StringUtils
0493: .equalsIgnoreCase(
0494: testPREQ.getStatusCode(),
0495: PaymentRequestStatuses.CANCELLED_IN_PROCESS)) {
0496: foundCanceledPreApprove |= true;
0497: } else {
0498: msgs
0499: .put(
0500: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0501: configurationService
0502: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT));
0503: addedMessage = true;
0504: break;
0505: }
0506: }
0507:
0508: // Custom error message for duplicates related to cancelled/voided PREQs
0509: if (!addedMessage) {
0510: if (foundCanceledPostApprove
0511: && foundCanceledPreApprove) {
0512: msgs
0513: .put(
0514: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0515: configurationService
0516: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLEDORVOIDED));
0517: } else if (foundCanceledPreApprove) {
0518: msgs
0519: .put(
0520: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0521: configurationService
0522: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_VOIDED));
0523: addedMessage = true;
0524: } else if (foundCanceledPostApprove) {
0525: msgs
0526: .put(
0527: PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION,
0528: configurationService
0529: .getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLED));
0530: addedMessage = true;
0531: }
0532:
0533: }
0534: }
0535: }
0536: return msgs;
0537: }
0538:
0539: /**
0540: * @see org.kuali.module.purap.service.PaymentRequestService#getPaymentRequestByDocumentNumber(java.lang.String)
0541: */
0542: public PaymentRequestDocument getPaymentRequestByDocumentNumber(
0543: String documentNumber) {
0544: LOG.debug("getPaymentRequestByDocumentNumber() started");
0545:
0546: if (ObjectUtils.isNotNull(documentNumber)) {
0547: try {
0548: PaymentRequestDocument doc = (PaymentRequestDocument) documentService
0549: .getByDocumentHeaderId(documentNumber);
0550: return doc;
0551: } catch (WorkflowException e) {
0552: String errorMessage = "Error getting payment request document from document service";
0553: LOG.error("getPaymentRequestByDocumentNumber() "
0554: + errorMessage, e);
0555: throw new RuntimeException(errorMessage, e);
0556: }
0557: }
0558: return null;
0559: }
0560:
0561: /**
0562: * @see org.kuali.module.purap.service.PaymentRequestService#getPaymentRequestById(java.lang.Integer)
0563: */
0564: public PaymentRequestDocument getPaymentRequestById(Integer poDocId) {
0565: return getPaymentRequestByDocumentNumber(paymentRequestDao
0566: .getDocumentNumberByPaymentRequestId(poDocId));
0567: }
0568:
0569: /**
0570: * @see org.kuali.module.purap.service.PaymentRequestService#getPaymentRequestsByPurchaseOrderId(java.lang.Integer)
0571: */
0572: public List<PaymentRequestDocument> getPaymentRequestsByPurchaseOrderId(
0573: Integer poDocId) {
0574: List<PaymentRequestDocument> preqs = new ArrayList<PaymentRequestDocument>();
0575: List<String> docNumbers = paymentRequestDao
0576: .getDocumentNumbersByPurchaseOrderId(poDocId);
0577: for (String docNumber : docNumbers) {
0578: PaymentRequestDocument preq = getPaymentRequestByDocumentNumber(docNumber);
0579: if (ObjectUtils.isNotNull(preq)) {
0580: preqs.add(preq);
0581: }
0582: }
0583: return preqs;
0584: }
0585:
0586: /**
0587: * @see org.kuali.module.purap.service.CreditMemoService#saveDocumentWithoutValidation(org.kuali.module.purap.document.CreditMemoDocument)
0588: */
0589: public void saveDocumentWithoutValidation(
0590: PaymentRequestDocument document) {
0591: try {
0592: documentService.saveDocument(document,
0593: DocumentSystemSaveEvent.class);
0594: } catch (WorkflowException we) {
0595: String errorMsg = "Error saving document # "
0596: + document.getDocumentHeader().getDocumentNumber()
0597: + " " + we.getMessage();
0598: LOG.error(errorMsg, we);
0599: throw new RuntimeException(errorMsg, we);
0600: }
0601: }
0602:
0603: /**
0604: * @see org.kuali.module.purap.service.PaymentRequestService#getPaymentRequestsByPOIdInvoiceAmountInvoiceDate(java.lang.Integer, org.kuali.core.util.KualiDecimal, java.sql.Date)
0605: */
0606: public List getPaymentRequestsByPOIdInvoiceAmountInvoiceDate(
0607: Integer poId, KualiDecimal invoiceAmount, Date invoiceDate) {
0608: LOG
0609: .debug("getPaymentRequestsByPOIdInvoiceAmountInvoiceDate() started");
0610: return paymentRequestDao
0611: .getActivePaymentRequestsByPOIdInvoiceAmountInvoiceDate(
0612: poId, invoiceAmount, invoiceDate);
0613: }
0614:
0615: /**
0616: * @see org.kuali.module.purap.service.PaymentRequestService#isInvoiceDateAfterToday(java.sql.Date)
0617: */
0618: public boolean isInvoiceDateAfterToday(Date invoiceDate) {
0619: // Check invoice date to make sure it is today or before
0620: Calendar now = Calendar.getInstance();
0621: now.set(Calendar.HOUR, 11);
0622: now.set(Calendar.MINUTE, 59);
0623: now.set(Calendar.SECOND, 59);
0624: now.set(Calendar.MILLISECOND, 59);
0625: Timestamp nowTime = new Timestamp(now.getTimeInMillis());
0626: Calendar invoiceDateC = Calendar.getInstance();
0627: invoiceDateC.setTime(invoiceDate);
0628: // set time to midnight
0629: invoiceDateC.set(Calendar.HOUR, 0);
0630: invoiceDateC.set(Calendar.MINUTE, 0);
0631: invoiceDateC.set(Calendar.SECOND, 0);
0632: invoiceDateC.set(Calendar.MILLISECOND, 0);
0633: Timestamp invoiceDateTime = new Timestamp(invoiceDateC
0634: .getTimeInMillis());
0635: return ((invoiceDateTime.compareTo(nowTime)) > 0);
0636: }
0637:
0638: /**
0639: * @see org.kuali.module.purap.service.PaymentRequestService#calculatePayDate(java.sql.Date, org.kuali.module.vendor.bo.PaymentTermType)
0640: */
0641: public Date calculatePayDate(Date invoiceDate, PaymentTermType terms) {
0642: LOG.debug("calculatePayDate() started");
0643: // calculate the invoice + processed calendar
0644: Calendar invoicedDateCalendar = dateTimeService
0645: .getCalendar(invoiceDate);
0646: Calendar processedDateCalendar = dateTimeService
0647: .getCurrentCalendar();
0648: // add default number of days to processed
0649: processedDateCalendar.add(Calendar.DAY_OF_MONTH,
0650: PurapConstants.PREQ_PAY_DATE_DEFAULT_NUMBER_OF_DAYS);
0651:
0652: if (ObjectUtils.isNull(terms)
0653: || StringUtils.isEmpty(terms
0654: .getVendorPaymentTermsCode())) {
0655: invoicedDateCalendar
0656: .add(
0657: Calendar.DAY_OF_MONTH,
0658: PurapConstants.PREQ_PAY_DATE_EMPTY_TERMS_DEFAULT_DAYS);
0659: return returnLaterDate(invoicedDateCalendar,
0660: processedDateCalendar);
0661: }
0662:
0663: Integer discountDueNumber = terms.getVendorDiscountDueNumber();
0664: Integer netDueNumber = terms.getVendorNetDueNumber();
0665: if (ObjectUtils.isNotNull(discountDueNumber)) {
0666: String discountDueTypeDescription = terms
0667: .getVendorDiscountDueTypeDescription();
0668: paymentTermsDateCalculation(discountDueTypeDescription,
0669: invoicedDateCalendar, discountDueNumber);
0670: } else if (ObjectUtils.isNotNull(netDueNumber)) {
0671: String netDueTypeDescription = terms
0672: .getVendorNetDueTypeDescription();
0673: paymentTermsDateCalculation(netDueTypeDescription,
0674: invoicedDateCalendar, netDueNumber);
0675: } else {
0676: throw new RuntimeException(
0677: "Neither discount or net number were specified for this payment terms type");
0678: }
0679:
0680: // return the later date
0681: return returnLaterDate(invoicedDateCalendar,
0682: processedDateCalendar);
0683: }
0684:
0685: /**
0686: * Returns whichever date is later, the invoicedDateCalendar or the processedDateCalendar.
0687: *
0688: * @param invoicedDateCalendar One of the dates to be used in determining which date is later.
0689: * @param processedDateCalendar The other date to be used in determining which date is later.
0690: * @return The date which is the later of the two given dates in the input parameters.
0691: */
0692: private Date returnLaterDate(Calendar invoicedDateCalendar,
0693: Calendar processedDateCalendar) {
0694: if (invoicedDateCalendar.after(processedDateCalendar)) {
0695: return new Date(invoicedDateCalendar.getTimeInMillis());
0696: } else {
0697: return new Date(processedDateCalendar.getTimeInMillis());
0698: }
0699: }
0700:
0701: /**
0702: * Calculates the paymentTermsDate given the dueTypeDescription, invoicedDateCalendar and
0703: * the dueNumber.
0704: *
0705: * @param dueTypeDescription The due type description of the payment term.
0706: * @param invoicedDateCalendar The Calendar object of the invoice date.
0707: * @param discountDueNumber Either the vendorDiscountDueNumber or the vendorDiscountDueNumber of the payment term.
0708: */
0709: private void paymentTermsDateCalculation(String dueTypeDescription,
0710: Calendar invoicedDateCalendar, Integer dueNumber) {
0711:
0712: if (StringUtils.equals(dueTypeDescription,
0713: PurapConstants.PREQ_PAY_DATE_DATE)) {
0714: // date specified set to date in next month
0715: invoicedDateCalendar.add(Calendar.MONTH, 1);
0716: invoicedDateCalendar.set(Calendar.DAY_OF_MONTH, dueNumber
0717: .intValue());
0718: } else if (StringUtils.equals(
0719: PurapConstants.PREQ_PAY_DATE_DAYS, dueTypeDescription)) {
0720: // days specified go forward that number
0721: invoicedDateCalendar.add(Calendar.DAY_OF_MONTH, dueNumber
0722: .intValue());
0723: } else {
0724: // improper string
0725: throw new RuntimeException(
0726: "missing payment terms description or not properly enterred on payment term maintenance doc");
0727: }
0728: }
0729:
0730: /**
0731: * @see org.kuali.module.purap.service.PaymentRequestService#calculatePaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument, boolean)
0732: */
0733: public void calculatePaymentRequest(
0734: PaymentRequestDocument paymentRequest,
0735: boolean updateDiscount) {
0736: LOG.debug("calculatePaymentRequest() started");
0737:
0738: if (ObjectUtils.isNull(paymentRequest
0739: .getPaymentRequestPayDate())) {
0740: paymentRequest.setPaymentRequestPayDate(calculatePayDate(
0741: paymentRequest.getInvoiceDate(), paymentRequest
0742: .getVendorPaymentTerms()));
0743: }
0744:
0745: if (updateDiscount) {
0746: calculateDiscount(paymentRequest);
0747: }
0748:
0749: distributeAccounting(paymentRequest);
0750: }
0751:
0752: /**
0753: * Calculates the discount item for this paymentRequest.
0754: *
0755: * @param paymentRequestDocument The payment request document whose discount to be calculated.
0756: */
0757: private void calculateDiscount(
0758: PaymentRequestDocument paymentRequestDocument) {
0759: PaymentRequestItem discountItem = findDiscountItem(paymentRequestDocument);
0760: // find out if we really need the discount item
0761: PaymentTermType pt = paymentRequestDocument
0762: .getVendorPaymentTerms();
0763: if ((pt != null)
0764: && (pt.getVendorPaymentTermsPercent() != null)
0765: && (BigDecimal.ZERO.compareTo(pt
0766: .getVendorPaymentTermsPercent()) != 0)) {
0767: if (discountItem == null) {
0768: // set discountItem and add to items
0769: // this is probably not the best way of doing it but should work for now if we start excluding discount from below
0770: // we will need to manually add
0771: SpringContext.getBean(PurapService.class)
0772: .addBelowLineItems(paymentRequestDocument);
0773: discountItem = findDiscountItem(paymentRequestDocument);
0774: }
0775: // discount item should no longer be null, update if necessary
0776: if (discountItem.getExtendedPrice().isZero()) {
0777: KualiDecimal totalCost = paymentRequestDocument
0778: .getTotalDollarAmountAboveLineItems();
0779: BigDecimal discountAmount = pt
0780: .getVendorPaymentTermsPercent()
0781: .multiply(totalCost.bigDecimalValue())
0782: .multiply(
0783: new BigDecimal(
0784: PurapConstants.PREQ_DISCOUNT_MULT));
0785: // do we really need to set both, not positive, but probably won't hurt
0786: discountItem.setItemUnitPrice(discountAmount.setScale(
0787: 2, KualiDecimal.ROUND_BEHAVIOR));
0788: discountItem.setExtendedPrice(new KualiDecimal(
0789: discountAmount));
0790: }
0791: } else { // no discount
0792: if (discountItem != null) {
0793: paymentRequestDocument.getItems().remove(discountItem);
0794: }
0795: }
0796:
0797: }
0798:
0799: /**
0800: * Finds the discount item of the payment request document.
0801: *
0802: * @param paymentRequestDocument The payment request document to be used to find the discount item.
0803: * @return The discount item if it exists.
0804: */
0805: private PaymentRequestItem findDiscountItem(
0806: PaymentRequestDocument paymentRequestDocument) {
0807: PaymentRequestItem discountItem = null;
0808: for (PaymentRequestItem preqItem : (List<PaymentRequestItem>) paymentRequestDocument
0809: .getItems()) {
0810: if (StringUtils
0811: .equals(
0812: preqItem.getItemTypeCode(),
0813: PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) {
0814: discountItem = preqItem;
0815: break;
0816: }
0817: }
0818: return discountItem;
0819: }
0820:
0821: /**
0822: * Distributes accounts for a payment request document.
0823: *
0824: * @param paymentRequestDocument
0825: */
0826: private void distributeAccounting(
0827: PaymentRequestDocument paymentRequestDocument) {
0828: // update the account amounts before doing any distribution
0829: SpringContext.getBean(PurapAccountingService.class)
0830: .updateAccountAmounts(paymentRequestDocument);
0831:
0832: for (PaymentRequestItem item : (List<PaymentRequestItem>) paymentRequestDocument
0833: .getItems()) {
0834: KualiDecimal totalAmount = KualiDecimal.ZERO;
0835: List<PurApAccountingLine> distributedAccounts = null;
0836: List<SourceAccountingLine> summaryAccounts = null;
0837:
0838: // skip above the line
0839: if (item.getItemType().isItemTypeAboveTheLineIndicator()) {
0840: continue;
0841: }
0842:
0843: if ((item.getSourceAccountingLines().isEmpty())
0844: && (ObjectUtils.isNotNull(item.getExtendedPrice()))
0845: && (KualiDecimal.ZERO.compareTo(item
0846: .getExtendedPrice()) != 0)) {
0847: if ((StringUtils
0848: .equals(
0849: PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE,
0850: item.getItemType().getItemTypeCode()))
0851: && (paymentRequestDocument.getGrandTotal() != null)
0852: && ((KualiDecimal.ZERO
0853: .compareTo(paymentRequestDocument
0854: .getGrandTotal()) != 0))) {
0855:
0856: totalAmount = paymentRequestDocument
0857: .getGrandTotal();
0858:
0859: summaryAccounts = SpringContext.getBean(
0860: PurapAccountingService.class)
0861: .generateSummary(
0862: paymentRequestDocument.getItems());
0863:
0864: distributedAccounts = SpringContext.getBean(
0865: PurapAccountingService.class)
0866: .generateAccountDistributionForProration(
0867: summaryAccounts, totalAmount,
0868: PurapConstants.PRORATION_SCALE,
0869: PaymentRequestAccount.class);
0870:
0871: } else {
0872:
0873: PurchaseOrderItem poi = item.getPurchaseOrderItem();
0874: if ((poi != null)
0875: && (poi.getSourceAccountingLines() != null)
0876: && (!(poi.getSourceAccountingLines()
0877: .isEmpty()))
0878: && (poi.getExtendedPrice() != null)
0879: && ((KualiDecimal.ZERO.compareTo(poi
0880: .getExtendedPrice())) != 0)) {
0881: // use accounts from purchase order item matching this item
0882: // account list of current item is already empty
0883: item.generateAccountListFromPoItemAccounts(poi
0884: .getSourceAccountingLines());
0885: } else {
0886: totalAmount = paymentRequestDocument
0887: .getPurchaseOrderDocument()
0888: .getTotalDollarAmountAboveLineItems();
0889: SpringContext
0890: .getBean(PurapAccountingService.class)
0891: .updateAccountAmounts(
0892: paymentRequestDocument
0893: .getPurchaseOrderDocument());
0894: summaryAccounts = SpringContext
0895: .getBean(PurapAccountingService.class)
0896: .generateSummary(
0897: PurApItemUtils
0898: .getAboveTheLineOnly(paymentRequestDocument
0899: .getPurchaseOrderDocument()
0900: .getItems()));
0901: distributedAccounts = SpringContext
0902: .getBean(PurapAccountingService.class)
0903: .generateAccountDistributionForProration(
0904: summaryAccounts, totalAmount,
0905: new Integer("6"),
0906: PaymentRequestAccount.class);
0907: }
0908:
0909: }
0910: if (CollectionUtils.isNotEmpty(distributedAccounts)
0911: && CollectionUtils.isEmpty(item
0912: .getSourceAccountingLines())) {
0913: item.setSourceAccountingLines(distributedAccounts);
0914: }
0915: }
0916: // update the item
0917: purapAccountingService.updateItemAccountAmounts(item);
0918: }
0919: // update again now that distribute is finished. (Note: we may not need this anymore now that I added updateItem line above
0920: purapAccountingService
0921: .updateAccountAmounts(paymentRequestDocument);
0922: }
0923:
0924: /**
0925: * @see org.kuali.module.purap.service.PaymentRequestService#addHoldOnPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument,
0926: * java.lang.String)
0927: */
0928: public void addHoldOnPaymentRequest(
0929: PaymentRequestDocument document, String note)
0930: throws Exception {
0931: // save the note
0932: Note noteObj = documentService.createNoteFromDocument(document,
0933: note);
0934: documentService.addNoteToDocument(document, noteObj);
0935: noteService.save(noteObj);
0936:
0937: // retrieve and save with hold indicator set to true
0938: PaymentRequestDocument preqDoc = getPaymentRequestByDocumentNumber(paymentRequestDao
0939: .getDocumentNumberByPaymentRequestId(document
0940: .getPurapDocumentIdentifier()));
0941: preqDoc.setHoldIndicator(true);
0942: preqDoc.setLastActionPerformedByUniversalUserId(GlobalVariables
0943: .getUserSession().getUniversalUser()
0944: .getPersonUniversalIdentifier());
0945: saveDocumentWithoutValidation(preqDoc);
0946:
0947: // must also save it on the incoming document
0948: document.setHoldIndicator(true);
0949: document
0950: .setLastActionPerformedByUniversalUserId(GlobalVariables
0951: .getUserSession().getUniversalUser()
0952: .getPersonUniversalIdentifier());
0953: }
0954:
0955: /**
0956: * @see org.kuali.module.purap.service.PaymentRequestService#removeHoldOnPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument)
0957: */
0958: public void removeHoldOnPaymentRequest(
0959: PaymentRequestDocument document, String note)
0960: throws Exception {
0961: // save the note
0962: Note noteObj = documentService.createNoteFromDocument(document,
0963: note);
0964: documentService.addNoteToDocument(document, noteObj);
0965: noteService.save(noteObj);
0966:
0967: // retrieve and save with hold indicator set to false
0968: PaymentRequestDocument preqDoc = getPaymentRequestByDocumentNumber(paymentRequestDao
0969: .getDocumentNumberByPaymentRequestId(document
0970: .getPurapDocumentIdentifier()));
0971: preqDoc.setHoldIndicator(false);
0972: preqDoc.setLastActionPerformedByUniversalUserId(null);
0973: saveDocumentWithoutValidation(preqDoc);
0974:
0975: // must also save it on the incoming document
0976: document.setHoldIndicator(false);
0977: document.setLastActionPerformedByUniversalUserId(null);
0978: }
0979:
0980: /**
0981: * @see org.kuali.module.purap.service.PaymentRequestService#canHoldPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument,
0982: * org.kuali.core.bo.user.UniversalUser)
0983: */
0984: public boolean canHoldPaymentRequest(
0985: PaymentRequestDocument document, UniversalUser user) {
0986: boolean canHold = false;
0987:
0988: String accountsPayableGroup = SpringContext
0989: .getBean(ParameterService.class)
0990: .getParameterValue(
0991: ParameterConstants.PURCHASING_DOCUMENT.class,
0992: PurapParameterConstants.Workgroups.WORKGROUP_ACCOUNTS_PAYABLE);
0993:
0994: /*
0995: * The user is an approver of the document, The user is a member of the Accounts Payable group
0996: */
0997: if (document.isHoldIndicator() == false
0998: && document.getPaymentRequestedCancelIndicator() == false
0999: && ((document.getDocumentHeader().hasWorkflowDocument() && (document
1000: .getDocumentHeader().getWorkflowDocument()
1001: .stateIsEnroute() && document
1002: .getDocumentHeader().getWorkflowDocument()
1003: .isApprovalRequested())) || (user
1004: .isMember(accountsPayableGroup) && document
1005: .getExtractedDate() == null))
1006: && (!PurapConstants.PaymentRequestStatuses.STATUSES_DISALLOWING_HOLD
1007: .contains(document.getStatusCode()))) {
1008:
1009: canHold = true;
1010: }
1011:
1012: return canHold;
1013: }
1014:
1015: /**
1016: * @see org.kuali.module.purap.service.PaymentRequestService#canRemoveHoldPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument,
1017: * org.kuali.core.bo.user.UniversalUser)
1018: */
1019: public boolean canRemoveHoldPaymentRequest(
1020: PaymentRequestDocument document, UniversalUser user) {
1021: boolean canRemoveHold = false;
1022:
1023: String accountsPayableSupervisorGroup = SpringContext
1024: .getBean(ParameterService.class)
1025: .getParameterValue(
1026: ParameterConstants.PURCHASING_DOCUMENT.class,
1027: PurapParameterConstants.Workgroups.WORKGROUP_ACCOUNTS_PAYABLE_SUPERVISOR);
1028:
1029: /*
1030: * The user is the person who put the preq on hold The user is a member of the AP Supervisor group
1031: */
1032: if (document.isHoldIndicator()
1033: && (user
1034: .getPersonUniversalIdentifier()
1035: .equals(
1036: document
1037: .getLastActionPerformedByUniversalUserId()) || user
1038: .isMember(accountsPayableSupervisorGroup))
1039: && (!PurapConstants.PaymentRequestStatuses.STATUSES_DISALLOWING_REMOVE_HOLD
1040: .contains(document.getStatusCode()))) {
1041:
1042: canRemoveHold = true;
1043: }
1044: return canRemoveHold;
1045: }
1046:
1047: /**
1048: * @see org.kuali.module.purap.service.PaymentRequestService#addHoldOnPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument,
1049: * java.lang.String)
1050: */
1051: public void requestCancelOnPaymentRequest(
1052: PaymentRequestDocument document, String note)
1053: throws Exception {
1054: // save the note
1055: Note noteObj = documentService.createNoteFromDocument(document,
1056: note);
1057: documentService.addNoteToDocument(document, noteObj);
1058: noteService.save(noteObj);
1059:
1060: // retrieve and save with hold indicator set to true
1061: PaymentRequestDocument preqDoc = getPaymentRequestByDocumentNumber(paymentRequestDao
1062: .getDocumentNumberByPaymentRequestId(document
1063: .getPurapDocumentIdentifier()));
1064: preqDoc.setPaymentRequestedCancelIndicator(true);
1065: preqDoc.setLastActionPerformedByUniversalUserId(GlobalVariables
1066: .getUserSession().getUniversalUser()
1067: .getPersonUniversalIdentifier());
1068: preqDoc
1069: .setAccountsPayableRequestCancelIdentifier(GlobalVariables
1070: .getUserSession().getUniversalUser()
1071: .getPersonUniversalIdentifier());
1072: saveDocumentWithoutValidation(preqDoc);
1073:
1074: // must also save it on the incoming document
1075: document.setPaymentRequestedCancelIndicator(true);
1076: document
1077: .setLastActionPerformedByUniversalUserId(GlobalVariables
1078: .getUserSession().getUniversalUser()
1079: .getPersonUniversalIdentifier());
1080: document
1081: .setAccountsPayableRequestCancelIdentifier(GlobalVariables
1082: .getUserSession().getUniversalUser()
1083: .getPersonUniversalIdentifier());
1084: }
1085:
1086: /**
1087: * @see org.kuali.module.purap.service.PaymentRequestService#removeHoldOnPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument)
1088: */
1089: public void removeRequestCancelOnPaymentRequest(
1090: PaymentRequestDocument document, String note)
1091: throws Exception {
1092: // save the note
1093: Note noteObj = documentService.createNoteFromDocument(document,
1094: note);
1095: documentService.addNoteToDocument(document, noteObj);
1096: noteService.save(noteObj);
1097:
1098: // retrieve and save with hold indicator set to false
1099: PaymentRequestDocument preqDoc = getPaymentRequestByDocumentNumber(paymentRequestDao
1100: .getDocumentNumberByPaymentRequestId(document
1101: .getPurapDocumentIdentifier()));
1102: clearRequestCancelFields(preqDoc);
1103: saveDocumentWithoutValidation(preqDoc);
1104:
1105: // must also save it on the incoming document
1106: clearRequestCancelFields(document);
1107: }
1108:
1109: /**
1110: * Clears the request cancel fields.
1111: *
1112: * @param document The payment request document whose request cancel fields to be cleared.
1113: */
1114: private void clearRequestCancelFields(
1115: PaymentRequestDocument document) {
1116: document.setPaymentRequestedCancelIndicator(false);
1117: document.setLastActionPerformedByUniversalUserId(null);
1118: document.setAccountsPayableRequestCancelIdentifier(null);
1119: }
1120:
1121: /**
1122: * @see org.kuali.module.purap.service.PaymentRequestService#isExtracted(org.kuali.module.purap.document.PaymentRequestDocument)
1123: */
1124: public boolean isExtracted(PaymentRequestDocument document) {
1125: return (ObjectUtils.isNull(document.getExtractedDate()) ? false
1126: : true);
1127: }
1128:
1129: /**
1130: * @see org.kuali.module.purap.service.PaymentRequestService#canHoldPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument,
1131: * org.kuali.core.bo.user.UniversalUser)
1132: */
1133: public boolean canUserRequestCancelOnPaymentRequest(
1134: PaymentRequestDocument document, UniversalUser user) {
1135:
1136: /*
1137: * The user is an approver of the document,
1138: */
1139: if (document.getPaymentRequestedCancelIndicator() == false
1140: && document.isHoldIndicator() == false
1141: && document.getExtractedDate() == null
1142: && (document.getDocumentHeader().hasWorkflowDocument() && (document
1143: .getDocumentHeader().getWorkflowDocument()
1144: .stateIsEnroute() && document
1145: .getDocumentHeader().getWorkflowDocument()
1146: .isApprovalRequested()))
1147: && (!PurapConstants.PaymentRequestStatuses.STATUSES_DISALLOWING_REQUEST_CANCEL
1148: .contains(document.getStatusCode()))) {
1149: return true;
1150: }
1151: return false;
1152: }
1153:
1154: /**
1155: * This method determines if a user has permission to remove a request for cancel on a payment request document.
1156: *
1157: * @see org.kuali.module.purap.service.PaymentRequestService#canRemoveHoldPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument,
1158: * org.kuali.core.bo.user.UniversalUser)
1159: */
1160: public boolean canUserRemoveRequestCancelOnPaymentRequest(
1161: PaymentRequestDocument document, UniversalUser user) {
1162: String accountsPayableSupervisorGroup = SpringContext
1163: .getBean(ParameterService.class)
1164: .getParameterValue(
1165: ParameterConstants.PURCHASING_DOCUMENT.class,
1166: PurapParameterConstants.Workgroups.WORKGROUP_ACCOUNTS_PAYABLE_SUPERVISOR);
1167:
1168: /*
1169: * The user is the person who requested a cancel on the preq The user is a member of the AP Supervisor group
1170: */
1171: if (document.getPaymentRequestedCancelIndicator()
1172: && (user
1173: .getPersonUniversalIdentifier()
1174: .equals(
1175: document
1176: .getLastActionPerformedByUniversalUserId()) || user
1177: .isMember(accountsPayableSupervisorGroup))
1178: && (!PurapConstants.PaymentRequestStatuses.STATUSES_DISALLOWING_REQUEST_CANCEL
1179: .contains(document.getStatusCode()))) {
1180: return true;
1181: }
1182: return false;
1183: }
1184:
1185: /**
1186: * @see org.kuali.module.purap.service.PaymentRequestService#cancelExtractedPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument, java.lang.String)
1187: */
1188: public void cancelExtractedPaymentRequest(
1189: PaymentRequestDocument paymentRequest, String note) {
1190: LOG.debug("cancelExtractedPaymentRequest() started");
1191: if (PaymentRequestStatuses.CANCELLED_STATUSES
1192: .contains(paymentRequest.getStatusCode())) {
1193: LOG.debug("cancelExtractedPaymentRequest() ended");
1194: return;
1195: }
1196:
1197: try {
1198: Note cancelNote = documentService.createNoteFromDocument(
1199: paymentRequest, note);
1200: documentService.addNoteToDocument(paymentRequest,
1201: cancelNote);
1202: SpringContext.getBean(NoteService.class).save(cancelNote);
1203: } catch (Exception e) {
1204: throw new RuntimeException(
1205: PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
1206: }
1207:
1208: //cancel extracted should not reopen PO
1209: paymentRequest.setReopenPurchaseOrderIndicator(false);
1210:
1211: SpringContext.getBean(AccountsPayableService.class)
1212: .cancelAccountsPayableDocument(paymentRequest, ""); // Performs save, so no explicit save is necessary
1213: LOG.debug("cancelExtractedPaymentRequest() PREQ "
1214: + paymentRequest.getPurapDocumentIdentifier()
1215: + " Cancelled Without Workflow");
1216: LOG.debug("cancelExtractedPaymentRequest() ended");
1217: }
1218:
1219: /**
1220: * @see org.kuali.module.purap.service.PaymentRequestService#resetExtractedPaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument, java.lang.String)
1221: */
1222: public void resetExtractedPaymentRequest(
1223: PaymentRequestDocument paymentRequest, String note) {
1224: LOG.debug("resetExtractedPaymentRequest() started");
1225: if (PaymentRequestStatuses.CANCELLED_STATUSES
1226: .contains(paymentRequest.getStatusCode())) {
1227: LOG.debug("resetExtractedPaymentRequest() ended");
1228: return;
1229: }
1230: paymentRequest.setExtractedDate(null);
1231: paymentRequest.setPaymentPaidDate(null);
1232: String noteText = "This Payment Request is being reset for extraction by PDP "
1233: + note;
1234: try {
1235: Note resetNote = documentService.createNoteFromDocument(
1236: paymentRequest, noteText);
1237: documentService
1238: .addNoteToDocument(paymentRequest, resetNote);
1239: SpringContext.getBean(NoteService.class).save(resetNote);
1240: } catch (Exception e) {
1241: throw new RuntimeException(
1242: PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
1243: }
1244: this .saveDocumentWithoutValidation(paymentRequest);
1245: LOG.debug("resetExtractedPaymentRequest() PREQ "
1246: + paymentRequest.getPurapDocumentIdentifier()
1247: + " Reset from Extracted status");
1248: }
1249:
1250: /**
1251: * @see org.kuali.module.purap.service.PaymentRequestService#populatePaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument)
1252: */
1253: public void populatePaymentRequest(
1254: PaymentRequestDocument paymentRequestDocument) {
1255:
1256: PurchaseOrderDocument purchaseOrderDocument = paymentRequestDocument
1257: .getPurchaseOrderDocument();
1258:
1259: // make a call to search for expired/closed accounts
1260: HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = SpringContext
1261: .getBean(AccountsPayableService.class)
1262: .getExpiredOrClosedAccountList(paymentRequestDocument);
1263:
1264: paymentRequestDocument.populatePaymentRequestFromPurchaseOrder(
1265: purchaseOrderDocument, expiredOrClosedAccountList);
1266:
1267: paymentRequestDocument
1268: .getDocumentHeader()
1269: .setFinancialDocumentDescription(
1270: createPreqDocumentDescription(
1271: paymentRequestDocument
1272: .getPurchaseOrderIdentifier(),
1273: paymentRequestDocument.getVendorName()));
1274:
1275: // write a note for expired/closed accounts if any exist and add a message stating there were expired/closed accounts at the
1276: // top of the document
1277: SpringContext.getBean(AccountsPayableService.class)
1278: .generateExpiredOrClosedAccountNote(
1279: paymentRequestDocument,
1280: expiredOrClosedAccountList);
1281:
1282: // set indicator so a message is displayed for accounts that were replaced due to expired/closed status
1283: if (!expiredOrClosedAccountList.isEmpty()) {
1284: paymentRequestDocument
1285: .setContinuationAccountIndicator(true);
1286: }
1287:
1288: // add discount item
1289: calculateDiscount(paymentRequestDocument);
1290: // distribute accounts (i.e. proration)
1291: distributeAccounting(paymentRequestDocument);
1292:
1293: }
1294:
1295: /**
1296: * A helper method to create the description for the payment request document.
1297: *
1298: * @param purchaseOrderIdentifier The purchase order identifier to be used as part of the description.
1299: * @param vendorName The vendor name to be used as part of the description.
1300: * @return The resulting description string for the payment request document.
1301: */
1302: private String createPreqDocumentDescription(
1303: Integer purchaseOrderIdentifier, String vendorName) {
1304: StringBuffer descr = new StringBuffer("");
1305: descr.append("PO: ");
1306: descr.append(purchaseOrderIdentifier);
1307: descr.append(" Vendor: ");
1308: descr.append(StringUtils.trimToEmpty(vendorName));
1309:
1310: int noteTextMaxLength = SpringContext.getBean(
1311: DataDictionaryService.class).getAttributeMaxLength(
1312: DocumentHeader.class,
1313: KFSPropertyConstants.FINANCIAL_DOCUMENT_DESCRIPTION)
1314: .intValue();
1315: if (noteTextMaxLength >= descr.length()) {
1316: return descr.toString();
1317: } else {
1318: return descr.toString().substring(0, noteTextMaxLength);
1319: }
1320: }
1321:
1322: /**
1323: * @see org.kuali.module.purap.service.PaymentRequestService#populateAndSavePaymentRequest(org.kuali.module.purap.document.PaymentRequestDocument)
1324: */
1325: public void populateAndSavePaymentRequest(
1326: PaymentRequestDocument preq) throws WorkflowException {
1327: try {
1328: preq
1329: .setStatusCode(PurapConstants.PaymentRequestStatuses.IN_PROCESS);
1330: documentService.saveDocument(preq,
1331: ContinueAccountsPayableEvent.class);
1332: } catch (ValidationException ve) {
1333: preq
1334: .setStatusCode(PurapConstants.PaymentRequestStatuses.INITIATE);
1335: } catch (WorkflowException we) {
1336: preq
1337: .setStatusCode(PurapConstants.PaymentRequestStatuses.INITIATE);
1338: String errorMsg = "Error saving document # "
1339: + preq.getDocumentHeader().getDocumentNumber()
1340: + " " + we.getMessage();
1341: LOG.error(errorMsg, we);
1342: throw new RuntimeException(errorMsg, we);
1343: }
1344: }
1345:
1346: /**
1347: * @see org.kuali.module.purap.service.PaymentRequestService#deleteSummaryAccounts(java.lang.Integer)
1348: */
1349: public void deleteSummaryAccounts(Integer purapDocumentIdentifier) {
1350: paymentRequestDao
1351: .deleteSummaryAccounts(purapDocumentIdentifier);
1352: }
1353:
1354: /**
1355: * If the full document entry has been completed and the status of the related purchase order document is closed, return true,
1356: * otherwise return false.
1357: *
1358: * @param apDoc The AccountsPayableDocument to be determined whether its purchase order should be reversed.
1359: * @return boolean true if the purchase order should be reversed.
1360: * @see org.kuali.module.purap.service.AccountsPayableDocumentSpecificService#shouldPurchaseOrderBeReversed
1361: * (org.kuali.module.purap.document.AccountsPayableDocument)
1362: */
1363: public boolean shouldPurchaseOrderBeReversed(
1364: AccountsPayableDocument apDoc) {
1365: PurchaseOrderDocument po = apDoc.getPurchaseOrderDocument();
1366: if (ObjectUtils.isNull(po)) {
1367: throw new RuntimeException(
1368: "po should never be null on PREQ");
1369: }
1370: // if past full entry and already closed return true
1371: if (purapService.isFullDocumentEntryCompleted(apDoc)
1372: && StringUtils.equalsIgnoreCase(
1373: PurapConstants.PurchaseOrderStatuses.CLOSED, po
1374: .getStatusCode())) {
1375: return true;
1376: }
1377: return false;
1378: }
1379:
1380: /**
1381: * @see org.kuali.module.purap.service.AccountsPayableDocumentSpecificService#getUniversalUserForCancel(org.kuali.module.purap.document.AccountsPayableDocument)
1382: */
1383: public UniversalUser getUniversalUserForCancel(
1384: AccountsPayableDocument apDoc) {
1385: PaymentRequestDocument preqDoc = (PaymentRequestDocument) apDoc;
1386: UniversalUser user = null;
1387: if (preqDoc.isPaymentRequestedCancelIndicator()) {
1388: user = preqDoc.getLastActionPerformedByUser();
1389: }
1390: return user;
1391: }
1392:
1393: /**
1394: * @see org.kuali.module.purap.service.AccountsPayableDocumentSpecificService#takePurchaseOrderCancelAction(org.kuali.module.purap.document.AccountsPayableDocument)
1395: */
1396: public void takePurchaseOrderCancelAction(
1397: AccountsPayableDocument apDoc) {
1398: PaymentRequestDocument preqDocument = (PaymentRequestDocument) apDoc;
1399: if (preqDocument.isReopenPurchaseOrderIndicator()) {
1400: String docType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT;
1401: purchaseOrderService
1402: .createAndRoutePotentialChangeDocument(
1403: preqDocument.getPurchaseOrderDocument()
1404: .getDocumentNumber(),
1405: docType,
1406: "reopened by Credit Memo "
1407: + apDoc
1408: .getPurapDocumentIdentifier()
1409: + "cancel",
1410: new ArrayList(),
1411: PurapConstants.PurchaseOrderStatuses.PENDING_REOPEN);
1412: }
1413: }
1414:
1415: /**
1416: * @see org.kuali.module.purap.service.AccountsPayableDocumentSpecificService#updateStatusByNode(java.lang.String,
1417: * org.kuali.module.purap.document.AccountsPayableDocument)
1418: */
1419: public String updateStatusByNode(String currentNodeName,
1420: AccountsPayableDocument apDoc) {
1421: return updateStatusByNode(currentNodeName,
1422: (PaymentRequestDocument) apDoc);
1423: }
1424:
1425: /**
1426: * Updates the status of the payment request document.
1427: *
1428: * @param currentNodeName The current node name.
1429: * @param preqDoc The payment request document whose status to be updated.
1430: * @return The canceled status code.
1431: */
1432: private String updateStatusByNode(String currentNodeName,
1433: PaymentRequestDocument preqDoc) {
1434: // remove request cancel if necessary
1435: clearRequestCancelFields(preqDoc);
1436:
1437: // update the status on the document
1438:
1439: String cancelledStatusCode = "";
1440: if (StringUtils.isEmpty(currentNodeName)) {
1441: // if empty probably not coming from workflow
1442: cancelledStatusCode = PurapConstants.PaymentRequestStatuses.CANCELLED_POST_AP_APPROVE;
1443: } else {
1444: NodeDetails currentNode = NodeDetailEnum
1445: .getNodeDetailEnumByName(currentNodeName);
1446: if (ObjectUtils.isNotNull(currentNode)) {
1447: cancelledStatusCode = currentNode
1448: .getDisapprovedStatusCode();
1449: }
1450: }
1451:
1452: if (StringUtils.isNotBlank(cancelledStatusCode)) {
1453: purapService.updateStatus(preqDoc, cancelledStatusCode);
1454: saveDocumentWithoutValidation(preqDoc);
1455: return cancelledStatusCode;
1456: } else {
1457: logAndThrowRuntimeException("No status found to set for document being disapproved in node '"
1458: + currentNodeName + "'");
1459: }
1460: return cancelledStatusCode;
1461: }
1462:
1463: /**
1464: * @see org.kuali.module.purap.service.PaymentRequestService#markPaid(org.kuali.module.purap.document.PaymentRequestDocument,
1465: * java.sql.Date)
1466: */
1467: public void markPaid(PaymentRequestDocument pr, Date processDate) {
1468: LOG.debug("markPaid() started");
1469:
1470: pr.setPaymentPaidDate(processDate);
1471: saveDocumentWithoutValidation(pr);
1472: }
1473:
1474: /**
1475: * @see org.kuali.module.purap.service.PaymentRequestService#hasDiscountItem(org.kuali.module.purap.document.PaymentRequestDocument)
1476: */
1477: public boolean hasDiscountItem(PaymentRequestDocument preq) {
1478: return ObjectUtils.isNotNull(findDiscountItem(preq));
1479: }
1480:
1481: /**
1482: * @see org.kuali.module.purap.service.AccountsPayableDocumentSpecificService#poItemEligibleForAp(org.kuali.module.purap.document.AccountsPayableDocument, org.kuali.module.purap.bo.PurchaseOrderItem)
1483: */
1484: public boolean poItemEligibleForAp(AccountsPayableDocument apDoc,
1485: PurchaseOrderItem poi) {
1486: if (ObjectUtils.isNull(poi)) {
1487: throw new RuntimeException(
1488: "item null in purchaseOrderItemEligibleForPayment ... this should never happen");
1489: }
1490: // if the po item is not active... skip it
1491: if (!poi.isItemActiveIndicator()) {
1492: return false;
1493: }
1494:
1495: ItemType poiType = poi.getItemType();
1496:
1497: if (poiType.isQuantityBasedGeneralLedgerIndicator()) {
1498: if (poi.getItemQuantity().isGreaterThan(
1499: poi.getItemInvoicedTotalQuantity())) {
1500: return true;
1501: }
1502: return false;
1503: } else { // not quantity based
1504: if (poi.getItemOutstandingEncumberedAmount().isGreaterThan(
1505: KualiDecimal.ZERO)) {
1506: return true;
1507: }
1508: return false;
1509: }
1510: }
1511:
1512: /**
1513: * Records the specified error message into the Log file and throws a runtime exception.
1514: *
1515: * @param errorMessage the error message to be logged.
1516: */
1517: protected void logAndThrowRuntimeException(String errorMessage) {
1518: this .logAndThrowRuntimeException(errorMessage, null);
1519: }
1520:
1521: /**
1522: * Records the specified error message into the Log file and throws the specified runtime exception.
1523: *
1524: * @param errorMessage the specified error message.
1525: * @param e the specified runtime exception.
1526: */
1527: protected void logAndThrowRuntimeException(String errorMessage,
1528: Exception e) {
1529: if (ObjectUtils.isNotNull(e)) {
1530: LOG.error(errorMessage, e);
1531: throw new RuntimeException(errorMessage, e);
1532: } else {
1533: LOG.error(errorMessage);
1534: throw new RuntimeException(errorMessage);
1535: }
1536: }
1537:
1538: }
|