Search in sources :

Example 6 with PaymentDetail

use of org.kuali.kfs.pdp.businessobject.PaymentDetail in project cu-kfs by CU-CommunityApps.

the class CuExtractPaymentServiceImpl method writeExtractBundledAchFile.

/**
 * A custom method that goes through and extracts all pending ACH payments and bundles them by payee/disbursement nbr.
 * Changes made to this method due to re-factoring the code so that common pieces could be used
 * by both ExtractPaymentServiceImpl.writeExtractAchFile and AchBundlerExtractPaymentServiceImpl.writeExtractBundledAchFile
 * as well as incorporating the Mellon file creation.
 * --Added the call to method writeExtractAchFileMellonBankFastTrack
 * --Added the call to writePayeeSpecificsToAchFile for re-factored code
 * --Added the call to writePaymentDetailToAchFile for re-factored code
 * --Made the "finally" clause match the ExtractPaymentServiceImpl.writeExtractAchFile finally so that the XML files are named the same regardless of which routine is invoked.
 * --Added call to get the parameterized bank notification email addresses
 *
 * @param extractedStatus
 * @param filename
 * @param processDate
 * @param sdf
 */
protected void writeExtractBundledAchFile(PaymentStatus extractedStatus, String filename, Date processDate, SimpleDateFormat sdf) {
    LOG.info("writeExtractBundledAchFile started.");
    BufferedWriter os = null;
    try {
        List<String> notificationEmailAddresses = getBankPaymentFileNotificationEmailAddresses();
        // Writes out the BNY Mellon Fast Track formatted file for ACH payments.
        // We need to do this first since the status is set in this method which
        // causes the writeExtractAchFileMellonBankFastTrack method to not find anything.
        writeExtractAchFileMellonBankFastTrack(extractedStatus, filename, processDate, sdf, notificationEmailAddresses);
        // totals for summary
        Map<String, Integer> unitCounts = new HashMap<String, Integer>();
        Map<String, KualiDecimal> unitTotals = new HashMap<String, KualiDecimal>();
        os = new BufferedWriter(new FileWriter(filename));
        os.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        writeOpenTag(os, 0, "achPayments");
        HashSet<String> bankCodes = getAchBundlerHelperService().getDistinctBankCodesForPendingAchPayments();
        for (String bankCode : bankCodes) {
            HashSet<Integer> disbNbrs = getAchBundlerHelperService().getDistinctDisbursementNumbersForPendingAchPaymentsByBankCode(bankCode);
            for (Iterator<Integer> iter = disbNbrs.iterator(); iter.hasNext(); ) {
                Integer disbursementNbr = iter.next();
                boolean first = true;
                KualiDecimal totalNetAmount = new KualiDecimal(0);
                // this seems wasteful, but since the total net amount is needed on the first payment detail...it's needed
                Iterator<PaymentDetail> i2 = getAchBundlerHelperService().getPendingAchPaymentDetailsByDisbursementNumberAndBank(disbursementNbr, bankCode);
                while (i2.hasNext()) {
                    PaymentDetail pd = i2.next();
                    totalNetAmount = totalNetAmount.add(pd.getNetPaymentAmount());
                }
                Iterator<PaymentDetail> paymentDetails = getAchBundlerHelperService().getPendingAchPaymentDetailsByDisbursementNumberAndBank(disbursementNbr, bankCode);
                while (paymentDetails.hasNext()) {
                    PaymentDetail paymentDetail = paymentDetails.next();
                    PaymentGroup paymentGroup = paymentDetail.getPaymentGroup();
                    if (!testMode) {
                        paymentGroup.setDisbursementDate(new java.sql.Date(processDate.getTime()));
                        paymentGroup.setPaymentStatus(extractedStatus);
                        businessObjectService.save(paymentGroup);
                    }
                    if (first) {
                        writePayeeSpecificsToAchFile(os, paymentGroup, processDate, sdf);
                        writeOpenTag(os, 4, "payments");
                    }
                    writePaymentDetailToAchFile(os, paymentGroup, paymentDetail, unitCounts, unitTotals, sdf);
                    first = false;
                }
                writeCloseTag(os, 4, "payments");
                // open for this tag is in method writePayeeSpecificsToAchFile
                writeCloseTag(os, 2, "ach");
            }
        }
        writeCloseTag(os, 0, "achPayments");
        getPaymentFileEmailService().sendAchSummaryEmail(unitCounts, unitTotals, dateTimeService.getCurrentDate());
    } catch (IOException ie) {
        LOG.error("MOD: extractAchFile() Problem reading file:  " + filename, ie);
        throw new IllegalArgumentException("Error writing to output file: " + ie.getMessage());
    } finally {
        // Close file
        if (os != null) {
            try {
                os.close();
                // Need to do this at the end to indicate that the file is NOT USED after it is closed.
                renameFile(filename, filename + ".NOT_USED");
            } catch (IOException ie) {
                // Not much we can do now
                LOG.error("IOException encountered in writeExtractBundledAchFile.  Message is: " + ie.getMessage());
            }
        }
    }
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) HashMap(java.util.HashMap) FileWriter(java.io.FileWriter) IOException(java.io.IOException) BufferedWriter(java.io.BufferedWriter) KualiInteger(org.kuali.rice.core.api.util.type.KualiInteger) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail) KualiDecimal(org.kuali.rice.core.api.util.type.KualiDecimal)

Example 7 with PaymentDetail

use of org.kuali.kfs.pdp.businessobject.PaymentDetail in project cu-kfs by CU-CommunityApps.

the class CuExtractPaymentServiceImpl method writeExtractAchFile.

@Override
protected void writeExtractAchFile(PaymentStatus extractedStatus, String filename, Date processDate, SimpleDateFormat sdf) {
    BufferedWriter os = null;
    try {
        List<String> notificationEmailAddresses = this.getBankPaymentFileNotificationEmailAddresses();
        // Writes out the BNY Mellon Fast Track formatted file for ACH payments.  We need to do this first since the status is set in this method which
        // causes the writeExtractAchFileMellonBankFastTrack method to not find anything.
        writeExtractAchFileMellonBankFastTrack(extractedStatus, filename, processDate, sdf, notificationEmailAddresses);
        // totals for summary
        Map<String, Integer> unitCounts = new HashMap<String, Integer>();
        Map<String, KualiDecimal> unitTotals = new HashMap<String, KualiDecimal>();
        Iterator iter = paymentGroupService.getByDisbursementTypeStatusCode(PdpConstants.DisbursementTypeCodes.ACH, PdpConstants.PaymentStatusCodes.PENDING_ACH);
        if (iter.hasNext()) {
            OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filename), "UTF-8");
            os = new BufferedWriter(writer);
            os.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            writeOpenTag(os, 0, "achPayments");
            while (iter.hasNext()) {
                PaymentGroup paymentGroup = (PaymentGroup) iter.next();
                if (!testMode) {
                    paymentGroup.setDisbursementDate(new java.sql.Date(processDate.getTime()));
                    paymentGroup.setPaymentStatus(extractedStatus);
                    businessObjectService.save(paymentGroup);
                }
                writePayeeSpecificsToAchFile(os, paymentGroup, processDate, sdf);
                // Write all payment level information
                writeOpenTag(os, 4, "payments");
                List<PaymentDetail> pdList = paymentGroup.getPaymentDetails();
                for (PaymentDetail paymentDetail : pdList) {
                    writePaymentDetailToAchFile(os, paymentGroup, paymentDetail, unitCounts, unitTotals, sdf);
                }
                writeCloseTag(os, 4, "payments");
                writeCloseTag(os, 2, "ach");
            }
            writeCloseTag(os, 0, "achPayments");
            paymentFileEmailService.sendAchSummaryEmail(unitCounts, unitTotals, dateTimeService.getCurrentDate());
        }
    } catch (IOException ie) {
        LOG.error("extractAchPayments() Problem reading file:  " + filename, ie);
        throw new IllegalArgumentException("Error writing to output file: " + ie.getMessage());
    } finally {
        // Close file
        if (os != null) {
            try {
                os.close();
            } catch (IOException ie) {
            // Not much we can do now
            }
        }
    }
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) HashMap(java.util.HashMap) IOException(java.io.IOException) BufferedWriter(java.io.BufferedWriter) KualiInteger(org.kuali.rice.core.api.util.type.KualiInteger) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail) FileOutputStream(java.io.FileOutputStream) Iterator(java.util.Iterator) KualiDecimal(org.kuali.rice.core.api.util.type.KualiDecimal) OutputStreamWriter(java.io.OutputStreamWriter)

Example 8 with PaymentDetail

use of org.kuali.kfs.pdp.businessobject.PaymentDetail in project cu-kfs by CU-CommunityApps.

the class CuProcessPdpCancelPaidServiceImpl method processPdpPaids.

/**
 * Overridden to process each payment detail in its own transaction.
 * This implementation forces the current service to call a proxied version of itself,
 * in order for Spring to handle the transactions properly on the per-payment handler method.
 *
 * @see org.kuali.kfs.pdp.batch.service.impl.ProcessPdpCancelPaidServiceImpl#processPdpPaids()
 */
@Override
public void processPdpPaids() {
    LOG.debug("processPdpPaids() started");
    CuProcessPdpCancelPaidService proxiedProcessPdpCancelPaidService = getProxiedProcessPdpCancelPaidService();
    Date processDate = dateTimeService.getCurrentSqlDate();
    List<ExtractionUnit> extractionUnits = getExtractionUnits();
    Iterator<PaymentDetail> details = paymentDetailService.getUnprocessedPaidDetails(extractionUnits);
    while (details.hasNext()) {
        PaymentDetail paymentDetail = details.next();
        proxiedProcessPdpCancelPaidService.processPdpPaid(paymentDetail, processDate);
    }
}
Also used : CuProcessPdpCancelPaidService(edu.cornell.kfs.pdp.batch.service.CuProcessPdpCancelPaidService) ExtractionUnit(org.kuali.kfs.pdp.businessobject.ExtractionUnit) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail) Date(java.sql.Date)

Example 9 with PaymentDetail

use of org.kuali.kfs.pdp.businessobject.PaymentDetail in project cu-kfs by CU-CommunityApps.

the class CuPaymentFileServiceImpl method checkForInactiveVendors.

/**
 * Checks whether any of the batch load's payment groups reference inactive vendors,
 * and sends warning emails appropriately if so.
 *
 * @param paymentGroups The payment groups that were loaded.
 * @param customer The customer's profile.
 */
private void checkForInactiveVendors(List<PaymentGroup> paymentGroups, CustomerProfile customer) {
    final int MESSAGE_START_SIZE = 300;
    StringBuilder inactiveVendorsMessage = new StringBuilder(MESSAGE_START_SIZE);
    for (PaymentGroup paymentGroup : paymentGroups) {
        // Determine whether the payment group's vendor is inactive.
        VendorDetail vendor = vendorService.getVendorDetail(paymentGroup.getPayeeId());
        if (vendor != null && !vendor.isActiveIndicator()) {
            // If vendor is inactive, then append warning text to final email message.
            LOG.warn("Found payment group with inactive vendor payee. Payment Group ID: " + paymentGroup.getId() + ", Vendor ID: " + paymentGroup.getPayeeId());
            String warnMessageStart = getStartOfVendorInactiveMessage(vendor);
            if (inactiveVendorsMessage.length() == 0) {
                // Add header if necessary.
                inactiveVendorsMessage.append("The PDP feed submitted by your unit includes payments to inactive vendors.  ").append("Action is needed on your part to rectify the situation for future payments.  ").append("Review the inactive reason to determine action needed.  Details follow:\n\n");
            }
            // Append payment detail information to the message. (As per the payment file XSD, there should be at least one detail.)
            for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
                inactiveVendorsMessage.append(warnMessageStart).append("Customer Payment Doc Nbr: ").append(paymentDetail.getCustPaymentDocNbr()).append('\n').append("Payment Group ID: ").append(paymentDetail.getPaymentGroupId()).append('\n').append("Payment Date: ").append(paymentGroup.getPaymentDate()).append('\n').append("Amount: ").append(paymentDetail.getNetPaymentAmount()).append("\n\n");
            }
        }
    }
    // If one or more inactive vendors were found, then send notification emails to warn about their presence.
    if (inactiveVendorsMessage.length() > 0) {
        sendInactiveVendorsMessage(customer, inactiveVendorsMessage.toString());
    }
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) VendorDetail(org.kuali.kfs.vnd.businessobject.VendorDetail) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail)

Example 10 with PaymentDetail

use of org.kuali.kfs.pdp.businessobject.PaymentDetail in project cu-kfs by CU-CommunityApps.

the class CuPaymentFileValidationServiceImpl method processGroupValidation.

@Override
protected void processGroupValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
    int groupCount = 0;
    for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
        groupCount++;
        int noteLineCount = 0;
        int detailCount = 0;
        // We've encountered Payment Files that have address lines exceeding the column size in DB table;
        // so adding extra validation on payment group BO, especially the max length, based on DD definitions.
        // Check that PaymentGroup String properties don't exceed maximum allowed length
        checkPaymentGroupPropertyMaxLength(paymentGroup, errorMap);
        // verify payee id and owner code if customer requires them to be filled in
        if (paymentFile.getCustomer().getPayeeIdRequired() && StringUtils.isBlank(paymentGroup.getPayeeId())) {
            LOG.debug("processGroupValidation, No payee");
            errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_ID_REQUIRED, Integer.toString(groupCount));
        }
        if (paymentFile.getCustomer().getOwnershipCodeRequired() && StringUtils.isBlank(paymentGroup.getPayeeOwnerCd())) {
            LOG.debug("processGroupValidation, no ownership code");
            errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_OWNER_CODE, Integer.toString(groupCount));
        }
        // validate payee id type
        if (StringUtils.isNotBlank(paymentGroup.getPayeeIdTypeCd())) {
            PayeeType payeeType = businessObjectService.findBySinglePrimaryKey(PayeeType.class, paymentGroup.getPayeeIdTypeCd());
            if (payeeType == null) {
                LOG.debug("processGroupValidation, no payee type");
                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_PAYEE_ID_TYPE, Integer.toString(groupCount), paymentGroup.getPayeeIdTypeCd());
            }
        }
        // validate vendor id and customer institution number
        if (paymentGroup.getPayeeId().split("-").length > 1) {
            try {
                paymentGroup.validateVendorIdAndCustomerInstitutionIdentifier();
            } catch (RuntimeException e1) {
                LOG.error("processGroupValidation, there was an error validating customer institution information", e1);
                errorMap.putError(KFSConstants.GLOBAL_ERRORS, CUKFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML, new String[] { e1.getMessage() });
            }
        } else {
            LOG.debug("processGroupValidation, found a non vendor number payee ID: " + paymentGroup.getPayeeId());
            if (cuPdpEmployeeService.shouldPayeeBeProcessedAsEmployeeForThisCustomer(paymentFile)) {
                Person employee = findPerson(paymentGroup.getPayeeId());
                if (ObjectUtils.isNull(employee)) {
                    LOG.error("processGroupValidation, unable to get a person from the employee id");
                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, CUPdpKeyConstants.ERROR_PDP_PAYMENTLOAD_INVALID_EMPLOYEE_ID, paymentGroup.getPayeeId());
                }
            }
        }
        // validate bank
        String bankCode = paymentGroup.getBankCode();
        if (StringUtils.isNotBlank(bankCode)) {
            Bank bank = bankService.getByPrimaryId(bankCode);
            if (bank == null) {
                LOG.debug("processGroupValidation, no bank");
                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_BANK_CODE, Integer.toString(groupCount), bankCode);
            } else if (!bank.isActive()) {
                LOG.debug("processGroupValidation, bank isn't active");
                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_BANK_CODE, Integer.toString(groupCount), bankCode);
            }
        }
        KualiDecimal groupTotal = KualiDecimal.ZERO;
        for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
            detailCount++;
            // Add a line to print the invoice number
            noteLineCount++;
            noteLineCount = noteLineCount + paymentDetail.getNotes().size();
            if ((paymentDetail.getNetPaymentAmount() == null) && (!paymentDetail.isDetailAmountProvided())) {
                paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
            } else if ((paymentDetail.getNetPaymentAmount() == null) && (paymentDetail.isDetailAmountProvided())) {
                paymentDetail.setNetPaymentAmount(paymentDetail.getCalculatedPaymentAmount());
            }
            // compare net to accounting segments
            if (paymentDetail.getAccountTotal().compareTo(paymentDetail.getNetPaymentAmount()) != 0) {
                LOG.debug("processGroupValidation, account total (" + paymentDetail.getAccountTotal() + ") not equal to net amount total (" + paymentDetail.getNetPaymentAmount() + ")");
                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DETAIL_TOTAL_MISMATCH, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getAccountTotal().toString(), paymentDetail.getNetPaymentAmount().toString());
            }
            // validate origin code if given
            if (StringUtils.isNotBlank(paymentDetail.getFinancialSystemOriginCode())) {
                OriginationCode originationCode = originationCodeService.getByPrimaryKey(paymentDetail.getFinancialSystemOriginCode());
                if (originationCode == null) {
                    LOG.debug("processGroupValidation, origination code is null");
                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_ORIGIN_CODE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialSystemOriginCode());
                }
            }
            // validate doc type if given
            if (StringUtils.isNotBlank(paymentDetail.getFinancialDocumentTypeCode())) {
                if (!documentTypeService.isActiveByName(paymentDetail.getFinancialDocumentTypeCode())) {
                    LOG.debug("processGroupValidation, " + paymentDetail.getFinancialDocumentTypeCode() + " is not active.");
                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_DOC_TYPE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialDocumentTypeCode());
                }
            }
            groupTotal = groupTotal.add(paymentDetail.getNetPaymentAmount());
        }
        // verify total for group is not negative
        if (groupTotal.doubleValue() < 0) {
            LOG.debug("processGroupValidation, group total less than zero");
            errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_NEGATIVE_GROUP_TOTAL, Integer.toString(groupCount));
        }
        // check that the number of detail items and note lines will fit on a check stub
        if (noteLineCount > getMaxNoteLines()) {
            LOG.debug("processGroupValidation, too many notes");
            errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_MAX_NOTE_LINES, Integer.toString(groupCount), Integer.toString(noteLineCount), Integer.toString(getMaxNoteLines()));
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("After processGroupValidation: " + printErrorMap(errorMap));
        }
    }
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) Bank(org.kuali.kfs.sys.businessobject.Bank) PayeeType(org.kuali.kfs.pdp.businessobject.PayeeType) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail) OriginationCode(org.kuali.kfs.sys.businessobject.OriginationCode) KualiDecimal(org.kuali.rice.core.api.util.type.KualiDecimal) Person(org.kuali.rice.kim.api.identity.Person)

Aggregations

PaymentDetail (org.kuali.kfs.pdp.businessobject.PaymentDetail)25 PaymentGroup (org.kuali.kfs.pdp.businessobject.PaymentGroup)11 KualiInteger (org.kuali.rice.core.api.util.type.KualiInteger)11 HashMap (java.util.HashMap)7 KualiDecimal (org.kuali.rice.core.api.util.type.KualiDecimal)7 Date (java.sql.Date)6 ArrayList (java.util.ArrayList)6 CustomerProfile (org.kuali.kfs.pdp.businessobject.CustomerProfile)6 BufferedWriter (java.io.BufferedWriter)5 IOException (java.io.IOException)5 GeneralLedgerPendingEntrySequenceHelper (org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)5 FileWriter (java.io.FileWriter)4 Date (java.util.Date)4 List (java.util.List)4 PaymentAccountDetail (org.kuali.kfs.pdp.businessobject.PaymentAccountDetail)4 PaymentNoteText (org.kuali.kfs.pdp.businessobject.PaymentNoteText)4 SimpleDateFormat (java.text.SimpleDateFormat)3 Map (java.util.Map)3 AccountingPeriod (org.kuali.kfs.coa.businessobject.AccountingPeriod)3 OffsetDefinition (org.kuali.kfs.coa.businessobject.OffsetDefinition)3