/* * 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.sys.batch.service.impl; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.kuali.kfs.fp.document.DisbursementVoucherDocument; import org.kuali.kfs.pdp.businessobject.Batch; import org.kuali.kfs.pdp.businessobject.CustomerProfile; import org.kuali.kfs.pdp.businessobject.PaymentGroup; import org.kuali.kfs.pdp.service.CustomerProfileService; import org.kuali.kfs.pdp.service.PdpEmailService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.batch.service.PaymentSourceExtractionService; import org.kuali.kfs.sys.batch.service.PaymentSourceToExtractService; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.PaymentSource; import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.core.api.util.type.KualiInteger; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kim.api.identity.principal.Principal; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentService; import org.springframework.transaction.annotation.Transactional; /** * This is the default implementation of the PaymentSourceExtractionService interface. */ @Transactional public class PaymentSourceExtractionServiceImpl implements PaymentSourceExtractionService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentSourceExtractionServiceImpl.class); protected DateTimeService dateTimeService; protected CustomerProfileService customerProfileService; protected BusinessObjectService businessObjectService; protected PdpEmailService paymentFileEmailService; protected PaymentSourceToExtractService<PaymentSource> paymentSourceToExtractService; protected DocumentService documentService; protected Set<String> checkAchFsloDocTypes; // This should only be set to true when testing this system. Setting this to true will run the code but // won't set the doc status to extracted boolean testMode = false; /** * This method extracts all payments from a disbursement voucher with a status code of "A" and uploads them as a batch for * processing. * * @return Always returns true if the method completes. * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#extractPayments() */ @Override public boolean extractPayments() { if (LOG.isDebugEnabled()) { LOG.debug("extractPayments() started"); } final Date processRunDate = dateTimeService.getCurrentDate(); final Principal uuser = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER); if (uuser == null) { if (LOG.isDebugEnabled()) { LOG.debug("extractPayments() Unable to find user " + KFSConstants.SYSTEM_USER); } throw new IllegalArgumentException("Unable to find user " + KFSConstants.SYSTEM_USER); } // Get a list of campuses that have documents with an 'A' (approved) status. Map<String, List<PaymentSource>> campusListMap = paymentSourceToExtractService.retrievePaymentSourcesByCampus(false); if (campusListMap != null && !campusListMap.isEmpty()) { // Process each campus one at a time for (String campusCode : campusListMap.keySet()) { extractPaymentsForCampus(campusCode, uuser.getPrincipalId(), processRunDate, campusListMap.get(campusCode)); } } return true; } /** * Pulls all disbursement vouchers with status of "A" and marked for immediate payment from the database and builds payment records for them * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#extractImmediatePayments() */ @Override public void extractImmediatePayments() { if (LOG.isDebugEnabled()) { LOG.debug("extractImmediatePayments() started"); } final Date processRunDate = dateTimeService.getCurrentDate(); final Principal uuser = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER); if (uuser == null) { LOG.debug("extractPayments() Unable to find user " + KFSConstants.SYSTEM_USER); throw new IllegalArgumentException("Unable to find user " + KFSConstants.SYSTEM_USER); } // Get a list of campuses that have documents with an 'A' (approved) status. Map<String, List<PaymentSource>> documentsByCampus = paymentSourceToExtractService.retrievePaymentSourcesByCampus(true); // Process each campus one at a time for (String campusCode : documentsByCampus.keySet()) { extractImmediatePaymentsForCampus(campusCode, uuser.getPrincipalId(), processRunDate, documentsByCampus.get(campusCode)); } } /** * This method extracts all outstanding payments from all the disbursement vouchers in approved status for a given campus and * adds these payments to a batch file that is uploaded for processing. * * @param campusCode The id code of the campus the payments will be retrieved for. * @param user The user object used when creating the batch file to upload with outstanding payments. * @param processRunDate This is the date that the batch file is created, often this value will be today's date. */ protected void extractPaymentsForCampus(String campusCode, String principalId, Date processRunDate, List<? extends PaymentSource> documents) { if (LOG.isDebugEnabled()) { LOG.debug("extractPaymentsForCampus() started for campus: " + campusCode); } Batch batch = createBatch(campusCode, principalId, processRunDate); Integer count = 0; KualiDecimal totalAmount = KualiDecimal.ZERO; for (PaymentSource document : documents) { if (getPaymentSourceToExtractService().shouldExtractPayment(document)) { addPayment(document, batch, processRunDate, false); count++; totalAmount = totalAmount.add(getPaymentSourceToExtractService().getPaymentAmount(document)); } } batch.setPaymentCount(new KualiInteger(count)); batch.setPaymentTotalAmount(totalAmount); businessObjectService.save(batch); paymentFileEmailService.sendLoadEmail(batch); } /** * Builds payment batch for Disbursement Vouchers marked as immediate * @param campusCode the campus code the disbursement vouchers should be associated with * @param user the user responsible building the payment batch (typically the System User, kfs) * @param processRunDate the time that the job to build immediate payments is run */ protected void extractImmediatePaymentsForCampus(String campusCode, String principalId, Date processRunDate, List<? extends PaymentSource> documents) { LOG.debug("extractImmediatesPaymentsForCampus() started for campus: " + campusCode); if (!documents.isEmpty()) { final PaymentSource firstPaymentSource = documents.get(0); Batch batch = createBatch(campusCode, principalId, processRunDate); Integer count = 0; KualiDecimal totalAmount = KualiDecimal.ZERO; for (PaymentSource document : documents) { if (getPaymentSourceToExtractService().shouldExtractPayment(document)) { addPayment(document, batch, processRunDate, false); count++; totalAmount = totalAmount.add(getPaymentSourceToExtractService().getPaymentAmount(document)); } } batch.setPaymentCount(new KualiInteger(count)); batch.setPaymentTotalAmount(totalAmount); businessObjectService.save(batch); paymentFileEmailService.sendLoadEmail(batch); } } /** * This method creates a payment group from the disbursement voucher and batch provided and persists that group to the database. * * @param document The document used to build a payment group detail. * @param batch The batch file used to build a payment group and detail. * @param processRunDate The date the batch file is to post. */ protected void addPayment(PaymentSource document, Batch batch, Date processRunDate, boolean immediate) { LOG.info("addPayment() started for document number=" + document.getDocumentNumber()); final java.sql.Date sqlProcessRunDate = new java.sql.Date(processRunDate.getTime()); PaymentGroup pg = getPaymentSourceToExtractService().createPaymentGroup(document, sqlProcessRunDate); if (pg != null) { // the payment source returned null instead of a PaymentGroup? I guess it didn't want to be paid for some reason (for instance, a 0 amount document or doc which didn't have a travel advance, etc) pg.setBatch(batch); if (immediate) { pg.setProcessImmediate(Boolean.TRUE); } this.businessObjectService.save(pg); if (!testMode) { getPaymentSourceToExtractService().markAsExtracted(document, sqlProcessRunDate, pg.getId()); } } } /** * This method creates a Batch instance and populates it with the information provided. * * @param campusCode The campus code used to retrieve a customer profile to be set on the batch. * @param orgCode the organization code used to retrieve a customer profile to be set on the batch. * @param subUnitCode the sub-unit code used to retrieve a customer profile to be set on the batch. * @param user The user who submitted the batch. * @param processRunDate The date the batch was submitted and the date the customer profile was generated. * @return A fully populated batch instance. */ protected Batch createBatch(String campusCode, String principalId, Date processRunDate) { final String orgCode = getPaymentSourceToExtractService().getPreDisbursementCustomerProfileUnit(); final String subUnitCode = getPaymentSourceToExtractService().getPreDisbursementCustomerProfileSubUnit(); CustomerProfile customer = customerProfileService.get(campusCode, orgCode, subUnitCode); if (customer == null) { throw new IllegalArgumentException("Unable to find customer profile for " + campusCode + "/" + orgCode + "/" + subUnitCode); } // Create the group for this campus Batch batch = new Batch(); batch.setCustomerProfile(customer); batch.setCustomerFileCreateTimestamp(new Timestamp(processRunDate.getTime())); batch.setFileProcessTimestamp(new Timestamp(processRunDate.getTime())); batch.setPaymentFileName(KFSConstants.DISBURSEMENT_VOUCHER_PDP_EXTRACT_FILE_NAME); batch.setSubmiterUserId(principalId); // Set these for now, we will update them later batch.setPaymentCount(KualiInteger.ZERO); batch.setPaymentTotalAmount(KualiDecimal.ZERO); businessObjectService.save(batch); return batch; } /** * This method retrieves a list of disbursement voucher documents that are in the status provided for the campus code given. * * @param statusCode The status of the disbursement vouchers to be retrieved. * @param campusCode The campus code that the disbursement vouchers will be associated with. * @param immediatesOnly only retrieve Disbursement Vouchers marked for immediate payment * @return A collection of disbursement voucher objects that meet the search criteria given. */ protected Collection<DisbursementVoucherDocument> getListByDocumentStatusCodeCampus(String statusCode, String campusCode, boolean immediatesOnly) { LOG.info("getListByDocumentStatusCodeCampus(statusCode=" + statusCode + ", campusCode=" + campusCode + ", immediatesOnly=" + immediatesOnly + ") started"); Collection<DisbursementVoucherDocument> list = new ArrayList<DisbursementVoucherDocument>(); try { Collection<DisbursementVoucherDocument> docs = SpringContext.getBean(FinancialSystemDocumentService.class).findByDocumentHeaderStatusCode(DisbursementVoucherDocument.class, statusCode); for (DisbursementVoucherDocument element : docs) { String dvdCampusCode = element.getCampusCode(); if (dvdCampusCode.equals(campusCode) && KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_CHECK.equals(element.getDisbVchrPaymentMethodCode())) { if ((immediatesOnly && element.isImmediatePaymentIndicator()) || !immediatesOnly) { list.add(element); } } } } catch (WorkflowException we) { LOG.error("Could not load Disbursement Voucher Documents with status code = " + statusCode + ": " + we); throw new RuntimeException(we); } return list; } /** * Extracts a single DisbursementVoucherDocument * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#extractImmediatePayment(org.kuali.kfs.fp.document.DisbursementVoucherDocument) */ @Override public void extractSingleImmediatePayment(PaymentSource paymentSource) { if (LOG.isDebugEnabled()) { LOG.debug("extractImmediatePayment(DisbursementVoucherDocument) started"); } if (getPaymentSourceToExtractService().shouldExtractPayment(paymentSource)) { final Date processRunDate = dateTimeService.getCurrentDate(); final Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER); if (principal == null) { LOG.debug("extractPayments() Unable to find user " + KFSConstants.SYSTEM_USER); throw new IllegalArgumentException("Unable to find user " + KFSConstants.SYSTEM_USER); } Batch batch = createBatch(paymentSource.getCampusCode(), principal.getPrincipalId(), processRunDate); KualiDecimal totalAmount = KualiDecimal.ZERO; addPayment(paymentSource, batch, processRunDate, true); totalAmount = totalAmount.add(getPaymentSourceToExtractService().getPaymentAmount(paymentSource)); batch.setPaymentCount(new KualiInteger(1)); batch.setPaymentTotalAmount(totalAmount); businessObjectService.save(batch); paymentFileEmailService.sendPaymentSourceImmediateExtractEmail(paymentSource, getPaymentSourceToExtractService().getImmediateExtractEMailFromAddress(), getPaymentSourceToExtractService().getImmediateExtractEmailToAddresses()); } } /** * This method sets the dateTimeService instance. * * @param dateTimeService The DateTimeService to be set. */ public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } /** * This method sets the customerProfileService instance. * * @param customerProfileService The CustomerProfileService to be set. */ public void setCustomerProfileService(CustomerProfileService customerProfileService) { this.customerProfileService = customerProfileService; } /** * Sets the businessObjectService attribute value. * * @param businessObjectService The businessObjectService to set. */ public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } /** * Sets the paymentFileEmailService attribute value. * * @param paymentFileEmailService The paymentFileEmailService to set. */ public void setPaymentFileEmailService(PdpEmailService paymentFileEmailService) { this.paymentFileEmailService = paymentFileEmailService; } /** * @return the injected implementation of the PaymentSourceToExtractService */ public PaymentSourceToExtractService<PaymentSource> getPaymentSourceToExtractService() { return this.paymentSourceToExtractService; } /** * Sets the paymentSourceToExtractService so that PaymentSources can be run through the extraction process * * @param paymentSourceToExtractService the paymentSourceToExtractService implementation to use */ public void setPaymentSourceToExtractService(PaymentSourceToExtractService<PaymentSource> paymentSourceToExtractService) { this.paymentSourceToExtractService = paymentSourceToExtractService; } /** * @return an implementation of the DocumentService */ public DocumentService getDocumentService() { return documentService; } /** * Sets the implementation of the DocumentService for this service to use * @param parameterService an implementation of DocumentService */ public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } }