/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.tem.document; import static org.kuali.kfs.module.tem.TemConstants.DISBURSEMENT_VOUCHER_DOCTYPE; import java.sql.Date; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.Column; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.fp.document.DisbursementVoucherDocument; import org.kuali.kfs.integration.purap.PurchasingAccountsPayableModuleService; import org.kuali.kfs.module.tem.TemConstants; import org.kuali.kfs.module.tem.TemKeyConstants; import org.kuali.kfs.module.tem.businessobject.ActualExpense; import org.kuali.kfs.module.tem.businessobject.ImportedExpense; import org.kuali.kfs.module.tem.businessobject.PerDiemExpense; import org.kuali.kfs.module.tem.businessobject.TemExpense; import org.kuali.kfs.module.tem.businessobject.TravelPayment; import org.kuali.kfs.module.tem.document.service.AccountingDocumentRelationshipService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.batch.service.PaymentSourceExtractionService; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail; import org.kuali.kfs.sys.businessobject.PaymentSourceWireTransfer; import org.kuali.kfs.sys.businessobject.WireCharge; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.PaymentSource; import org.kuali.kfs.sys.document.service.PaymentSourceHelperService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kim.api.identity.PersonService; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.util.ObjectUtils; public abstract class TEMReimbursementDocument extends TravelDocumentBase implements PaymentSource { private String paymentMethod = KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_CHECK; private TravelPayment travelPayment; private PaymentSourceWireTransfer wireTransfer; private volatile transient Person initiator; private Date corporateCardPaymentExtractDate; private Date corporateCardPaymentPaidDate; private Date corporateCardPaymentCancelDate; private static transient volatile PaymentSourceHelperService paymentSourceHelperService; private static transient volatile PaymentSourceExtractionService paymentSourceExtractionService; private static transient volatile AccountingDocumentRelationshipService accountingDocumentRelationshipService; private static transient volatile PurchasingAccountsPayableModuleService purapModuleService; @Column(name = "PAYMENT_METHOD", nullable = true, length = 15) public String getPaymentMethod() { return paymentMethod; } public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; } /** * @return the travel payment associated with this document */ public TravelPayment getTravelPayment() { return travelPayment; } /** * Sets the travel payment to be associated with this document * @param travelPayment a travel payment for this document */ public void setTravelPayment(TravelPayment travelPayment) { this.travelPayment = travelPayment; } /** * @return the wire transfer associated with this document */ @Override public PaymentSourceWireTransfer getWireTransfer() { return wireTransfer; } /** * Sets the wire transfer associated with this document * @param wireTransfer the wire transfer for this document */ public void setWireTransfer(PaymentSourceWireTransfer wireTransfer) { this.wireTransfer = wireTransfer; } /** * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#initiateDocument() */ @Override public void initiateDocument() { super.initiateDocument(); //clear expenses setActualExpenses(new ArrayList<ActualExpense>()); setPerDiemExpenses(new ArrayList<PerDiemExpense>()); //default dates if null Calendar calendar = getDateTimeService().getCurrentCalendar(); if (getTripBegin() == null) { calendar.add(Calendar.DAY_OF_MONTH, 1); setTripBegin(new Timestamp(calendar.getTimeInMillis())); } if (getTripEnd() == null) { calendar.add(Calendar.DAY_OF_MONTH, 2); setTripEnd(new Timestamp(calendar.getTimeInMillis())); } // initiate payment and wire transfer this.travelPayment = new TravelPayment(); this.wireTransfer = new PaymentSourceWireTransfer(); // set due date to the next day calendar = getDateTimeService().getCurrentCalendar(); calendar.add(Calendar.DAY_OF_MONTH, 1); travelPayment.setDueDate(new java.sql.Date(calendar.getTimeInMillis())); travelPayment.setCheckStubText(getConfigurationService().getPropertyValueAsString(TemKeyConstants.MESSAGE_TEM_REIMBURSEMENT_PAYMENT_HOLD_TEXT)); // set up the default bank setDefaultBankCode(); updatePayeeTypeForReimbursable(); } /** * For reimbursable documents, sets the proper payee type code and profile id after a profile lookup */ public void updatePayeeTypeForReimbursable() { if (!ObjectUtils.isNull(getTraveler()) && !ObjectUtils.isNull(getTravelPayment()) && !StringUtils.isBlank(getTraveler().getTravelerTypeCode())) { if (getTravelerService().isEmployee(getTraveler())){ getTravelPayment().setPayeeTypeCode(KFSConstants.PaymentPayeeTypes.EMPLOYEE); }else{ getTravelPayment().setPayeeTypeCode(KFSConstants.PaymentPayeeTypes.CUSTOMER); } } } /** * change to generate the GLPE on ACTUAL EXPENSE based on Reimbursable minus Advance and its only ONE entry to * DV clearning account * * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#generateDocumentGeneralLedgerPendingEntries * (org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper) */ @Override public boolean generateDocumentGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySequenceHelper sequenceHelper) { LOG.info("processDocumentGenerateGeneralLedgerPendingEntries for TEMReimbursementDocument - start"); boolean success = true; boolean doGenerate = true; success = super.generateDocumentGeneralLedgerPendingEntries(sequenceHelper); KualiDecimal reimbursableToTraveler = getAmountForTravelClearing(); if (KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_WIRE.equals(getTravelPayment().getPaymentMethodCode()) && !getWireTransfer().isWireTransferFeeWaiverIndicator()) { LOG.debug("generating wire charge gl pending entries."); // retrieve wire charge WireCharge wireCharge = getPaymentSourceHelperService().retrieveCurrentYearWireCharge(); // generate debits GeneralLedgerPendingEntry chargeEntry = getPaymentSourceHelperService().processWireChargeDebitEntries(this, sequenceHelper, wireCharge); // generate credits getPaymentSourceHelperService().processWireChargeCreditEntries(this, sequenceHelper, wireCharge, chargeEntry); } // for wire or drafts generate bank offset entry (if enabled), for ACH and checks offset will be generated by PDP if (KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_WIRE.equals(getTravelPayment().getPaymentMethodCode()) || KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_DRAFT.equals(getTravelPayment().getPaymentMethodCode())) { getPaymentSourceHelperService().generateDocumentBankOffsetEntries(this, sequenceHelper, getWireTransferOrForeignDraftDocumentType()); } LOG.info("processDocumentGenerateGeneralLedgerPendingEntries for TEMReimbursementDocument - end"); return success; } /** * @return the amount that should be used in the travel clearing pending entries */ protected KualiDecimal getAmountForTravelClearing() { KualiDecimal reimbursableToTraveler = getTravelReimbursementService().getReimbursableToTraveler(this); return reimbursableToTraveler; } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#customizeExplicitGeneralLedgerPendingEntry(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry) */ @Override public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) { /* change document type based on payment method to pick up different offsets; if payment method code is blank, use the normal doc type name */ explicitEntry.setFinancialDocumentTypeCode(getPaymentDocumentType()); if (LOG.isDebugEnabled()) { LOG.debug("changing doc type on pending entry " + explicitEntry.getTransactionLedgerEntrySequenceNumber() + " to " + explicitEntry.getFinancialDocumentTypeCode()); } } /** * @return the document type to use for entries associated with this document, which is based, if possible, on the payment type */ public String getPaymentDocumentType() { if (!StringUtils.isBlank(getTravelPayment().getPaymentMethodCode())) { if (KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_CHECK.equals(getTravelPayment().getPaymentMethodCode())) { return getAchCheckDocumentType(); } return getWireTransferOrForeignDraftDocumentType(); } return getDocumentTypeName(); } /** * @return the FSLO document type associated with ach/check entries for this document type */ public abstract String getAchCheckDocumentType(); /** * @return the FSLO document type associated with wire transfer or foreign draft entries for this document type */ public abstract String getWireTransferOrForeignDraftDocumentType(); /** * @return RCCA, the corporate card payment ach/check document type */ public String getCorporateCardPaymentAchCheckDocumentType() { return TemConstants.TravelDocTypes.REIMBURSABLE_CORPORATE_CARD_CHECK_ACH_DOCUMENT; } /** * @see org.kuali.kfs.module.tem.document.TravelDocument#getReimbursableTotal() */ @Override public KualiDecimal getReimbursableTotal() { KualiDecimal eligible = getEligibleAmount(); final KualiDecimal expenseLimit = getExpenseLimit(); if (expenseLimit != null && expenseLimit.doubleValue() > 0) { return eligible.isGreaterThan(expenseLimit) ? expenseLimit : eligible; } return eligible; } /** * Get eligible amount * * @return */ public KualiDecimal getEligibleAmount(){ return getApprovedAmount().subtract(getCTSTotal()).subtract(getCorporateCardTotal()); } /** * Total amount to be paid to Vendor * * @return */ public KualiDecimal getTotalPaidAmountToVendor() { KualiDecimal totalPaidAmountToVendor = KualiDecimal.ZERO; List<Document> relatedDisbursementList = getTravelDocumentService().getDocumentsRelatedTo(this, DISBURSEMENT_VOUCHER_DOCTYPE); for (Document document : relatedDisbursementList) { if (document instanceof DisbursementVoucherDocument) { DisbursementVoucherDocument dv = (DisbursementVoucherDocument) document; if (dv.getDocumentHeader().getWorkflowDocument().isFinal()) { totalPaidAmountToVendor = totalPaidAmountToVendor.add(dv.getDisbVchrCheckTotalAmount()); } } } return totalPaidAmountToVendor; } /** * Total requested to be paid * * @return */ public KualiDecimal getTotalPaidAmountToRequests() { KualiDecimal totalPaidAmountToRequests = KualiDecimal.ZERO; Set<String> documentNumbers = getAccountingDocumentRelationshipService().huntForRelatedDocumentNumbersWithDocumentType(documentNumber, TemConstants.REQUISITION_DOCTYPE); if (documentNumbers != null && !documentNumbers.isEmpty()) { List<String> relatedRequisitionDocumentNumbers = new ArrayList<String>(); relatedRequisitionDocumentNumbers.addAll(documentNumbers); totalPaidAmountToRequests = getPurchasingAccountsPayableModuleService().getTotalPaidAmountToRequisitions(relatedRequisitionDocumentNumbers); } return totalPaidAmountToRequests; } /** * * @return */ public KualiDecimal getReimbursableGrandTotal() { KualiDecimal grandTotal = KualiDecimal.ZERO; grandTotal = getApprovedAmount().add(getTotalPaidAmountToVendor()).add(getTotalPaidAmountToRequests()); if (!grandTotal.isPositive()) { return KualiDecimal.ZERO; } return grandTotal; } /** * @return the amount that should be disbursed to the traveler for this reimbursement */ public KualiDecimal getPaymentAmount() { final KualiDecimal reimbursableExpenseTotal = getEligibleAmount(); if (reimbursableExpenseTotal.isLessThan(KualiDecimal.ZERO)) { return KualiDecimal.ZERO; } final KualiDecimal reimbursableExpenseTotalWithExpenseLimit = applyExpenseLimit(reimbursableExpenseTotal); return reimbursableExpenseTotalWithExpenseLimit; } /** * * @return */ public boolean requiresTravelerApprovalRouting() { return getTravelDocumentService().requiresTravelerApproval(this); } /** * @see org.kuali.kfs.module.tem.document.TravelDocument#getPerDiemAdjustment() */ @Override public KualiDecimal getPerDiemAdjustment() { //do not use per diem adjustment return null; } /** * @see org.kuali.kfs.module.tem.document.TravelDocument#setPerDiemAdjustment(org.kuali.rice.kns.util.KualiDecimal) */ @Override public void setPerDiemAdjustment(KualiDecimal perDiemAdjustment) { } /** * @see org.kuali.kfs.module.tem.document.TravelDocument#getExpenseTypeCode() */ @Override public String getDefaultCardTypeCode() { return TemConstants.ACTUAL_EXPENSE; } /** * @return the value of the travelPayment's attachmentCode */ @Override public boolean hasAttachment() { return getTravelPayment().isAttachmentCode(); } /** * @return the payment method code from the travelPayment */ @Override public String getPaymentMethodCode() { return getTravelPayment().getPaymentMethodCode(); } public Date getCorporateCardPaymentExtractDate() { return corporateCardPaymentExtractDate; } public void setCorporateCardPaymentExtractDate(Date corporateCardPaymentExtractDate) { this.corporateCardPaymentExtractDate = corporateCardPaymentExtractDate; } public Date getCorporateCardPaymentPaidDate() { return corporateCardPaymentPaidDate; } public void setCorporateCardPaymentPaidDate(Date corporateCardPaymentPaidDate) { this.corporateCardPaymentPaidDate = corporateCardPaymentPaidDate; } public Date getCorporateCardPaymentCancelDate() { return corporateCardPaymentCancelDate; } public void setCorporateCardPaymentCancelDate(Date corporateCardPaymentCancelDate) { this.corporateCardPaymentCancelDate = corporateCardPaymentCancelDate; } /* * Used for searching on ENT and RELO. * */ public String getTemProfileName() { if (!ObjectUtils.isNull(getTraveler()) && !StringUtils.isBlank(getTraveler().getName())) { return getTraveler().getName().toUpperCase(); } return KFSConstants.EMPTY_STRING; } /** * Returns the campus code of the initiator * @see org.kuali.kfs.sys.document.PaymentSource#getCampusCode() */ @Override public String getCampusCode() { return getInitiator().getCampusCode(); } /** * Caches and returns the Person record of the initiator of this document * @return a Person record for the initiator of this document */ protected Person getInitiator() { if (initiator == null) { initiator = SpringContext.getBean(PersonService.class).getPerson(getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId()); } return initiator; } /** * Overridden to set document number on travel payment and wire transfer * @see org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase#prepareForSave() */ @Override public void prepareForSave() { super.prepareForSave(); if (wireTransfer != null) { wireTransfer.setDocumentNumber(this.documentNumber); } if (travelPayment != null) { travelPayment.setDocumentNumber(this.documentNumber); } } /** * Overridden to extract immediate payment if necerssary * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#doRouteStatusChange(org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange) */ @Override public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) { super.doRouteStatusChange(statusChangeEvent); if (getDocumentHeader().getWorkflowDocument().isProcessed()) { if (getTravelPayment().isImmediatePaymentIndicator()) { getPaymentSourceExtractionService().extractSingleImmediatePayment(this); } } } /** * Every reimbursable document debits on entry * @see org.kuali.kfs.sys.document.AccountingDocumentBase#isDebit(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail) */ @Override public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) { return true; } /** * Determines if any imported expenses on the document have a card type of CORP * @return true if there are CORP imported expenses on the document, false otherwise */ public boolean isCorporateCardPayable() { if (getImportedExpenses() != null && !getImportedExpenses().isEmpty()) { for (ImportedExpense expense : getImportedExpenses()) { if (StringUtils.equals(expense.getCardType(), TemConstants.TRAVEL_TYPE_CORP)) { return true; } } } return false; } /** * Based on which pdp dates are present (extract, paid, canceled), determines a String for the status * @return a String representation of the status */ public String getCorporateCardPaymentPdpStatus() { if (corporateCardPaymentCancelDate != null) { return "Canceled"; } if (corporateCardPaymentPaidDate != null) { return "Paid"; } if (corporateCardPaymentExtractDate != null) { return "Extracted"; } return "Pre-Extraction"; } /** * Reimbursable documents route by profile account if they have no actual expenses, per diem expenses, or reimbursable imported expenses * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#shouldRouteByProfileAccount() */ @Override protected boolean shouldRouteByProfileAccount() { final boolean shouldRouteByProfileAccount = !hasReimbursableExpenses() || hasOnlyPrepaidExpenses(); return shouldRouteByProfileAccount; } /** * @return true if there are reimbursable expenses on this document and false if there are not */ public boolean hasReimbursableExpenses() { final boolean actualExpenses = hasReimbursableExpenses(getActualExpenses()); final boolean perDiemExpenses = getPerDiemExpenses() != null && !getPerDiemExpenses().isEmpty(); final boolean reimbursableImportedExpenses = hasReimbursableExpenses(getImportedExpenses()); return (actualExpenses || perDiemExpenses || reimbursableImportedExpenses); } /** * @return true if there are reimbursable imported expenses; false otherwise */ protected boolean hasReimbursableExpenses(List<? extends TemExpense> expenses) { if (expenses == null || expenses.isEmpty()) { return false; // no imported expenses; let's just skip out now... } for (TemExpense expense : expenses) { if (!expense.getNonReimbursable()) { return true; } } return false; } /** * Return the expense date if it exists * @see org.kuali.kfs.module.tem.document.TravelDocument#getEffectiveDateForMileageRate(org.kuali.kfs.module.tem.businessobject.ActualExpense) */ @Override public Date getEffectiveDateForMileageRate(ActualExpense expense) { if (expense != null && expense.getExpenseDate() != null) { return expense.getExpenseDate(); } return null; } /** * Returns the "mileage date" on the expense if it exists * @see org.kuali.kfs.module.tem.document.TravelDocument#getEffectiveDateForMileageRate(org.kuali.kfs.module.tem.businessobject.PerDiemExpense) */ @Override public Date getEffectiveDateForMileageRate(PerDiemExpense expense) { if (expense != null && expense.getMileageDate() != null) { return new java.sql.Date(expense.getMileageDate().getTime()); } return null; } /** * Returns the mileage expense date if it exists * @see org.kuali.kfs.module.tem.document.TravelDocument#getEffectiveDateForPerDiem(org.kuali.kfs.module.tem.businessobject.PerDiemExpense) */ @Override public Date getEffectiveDateForPerDiem(PerDiemExpense expense) { if (expense != null && expense.getMileageDate() != null) { return getEffectiveDateForPerDiem(expense.getMileageDate()); } return null; } /** * Just returns the date back * @see org.kuali.kfs.module.tem.document.TravelDocument#getEffectiveDateForPerDiem(java.sql.Date) */ @Override public Date getEffectiveDateForPerDiem(java.sql.Timestamp expenseDate) { return new java.sql.Date(expenseDate.getTime()); } @Override public Map<String, String> getDisapprovedAppDocStatusMap() { // TODO Auto-generated method stub return null; } /** * @return the default implementation of the PaymentSourceHelperService */ public static PaymentSourceHelperService getPaymentSourceHelperService() { if (paymentSourceHelperService == null) { paymentSourceHelperService = SpringContext.getBean(PaymentSourceHelperService.class); } return paymentSourceHelperService; } /** * @return the default implementation of the PaymentSourceHelperService */ public static PaymentSourceExtractionService getPaymentSourceExtractionService() { if (paymentSourceExtractionService == null) { paymentSourceExtractionService = SpringContext.getBean(PaymentSourceExtractionService.class, TemConstants.REIMBURSABLE_PAYMENT_SOURCE_EXTRACTION_SERVICE); } return paymentSourceExtractionService; } public static AccountingDocumentRelationshipService getAccountingDocumentRelationshipService() { if (accountingDocumentRelationshipService == null) { accountingDocumentRelationshipService = SpringContext.getBean(AccountingDocumentRelationshipService.class); } return accountingDocumentRelationshipService; } public static PurchasingAccountsPayableModuleService getPurchasingAccountsPayableModuleService() { if (purapModuleService == null) { purapModuleService = SpringContext.getBean(PurchasingAccountsPayableModuleService.class); } return purapModuleService; } }