Search in sources :

Example 21 with PaymentGroup

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

the class CuExtractPaymentServiceImpl method writeExtractCheckFileMellonBankFastTrack.

// This method is called by the method that generates the XML file for checks to be printed by BNY Mellon
protected void writeExtractCheckFileMellonBankFastTrack(PaymentStatus extractedStatus, PaymentProcess p, String filename, Integer processId, List<String> notificationEmailAddresses) {
    // Used in the Fast Track file HEADER record
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    // Used in the Fast Track file PAY01000 record
    SimpleDateFormat sdfPAY1000Rec = new SimpleDateFormat("yyyyMMdd");
    // Used in the issuance file
    SimpleDateFormat sdfIDate = new SimpleDateFormat("yyMMdd");
    // Used in the issuance file (NO SECONDS)
    SimpleDateFormat sdfITime = new SimpleDateFormat("HHmm");
    Date processDate = dateTimeService.getCurrentDate();
    BufferedWriter os = null;
    BufferedWriter osI = null;
    // column delimiter: Per BNY Mellon FastTrack spec, your choices are: "^" or ",".  If you change this make sure you change the associated name on the next line!
    String cDelim = "^";
    // column delimiter name: Per BNY Mellon FastTrack spec, your choices are: FFCARET and FFCOMMA for variable record types
    String cDname = "FFCARET";
    // record type: Per BNY Mellon's FastTrack spec, can be either V for variable or F for fixed.
    String hdrRecType = "V";
    // For Mellon Fast Track files - indicates whether the generated file is for (T)est or for (P)roduction
    String testIndicator;
    String ourBankAccountNumber = "";
    String ourBankRoutingNumber = "";
    String subUnitCode = "";
    String divisionCode = "";
    boolean specialHandlingCode = false;
    boolean attachmentCode = false;
    boolean immediateCheckCode = false;
    String PreparerInfoText = "";
    String altAddrSendTo = "";
    String altAddrAddr1 = "";
    String altAddrAddr2 = "";
    String altAddrAddr3 = "";
    String altAddrAddr4 = "";
    String altAddrCity = "";
    String altAddrState = "";
    String altAddrZip = "";
    String altCountryName = "";
    // this is needed because the notes combine these into one field
    String altAddrCityStateZip = "";
    String altRefQualifer = "";
    int NumOfAltAddressLines = 0;
    int SendToPrefLength = 0;
    // Note lines that are not the alternate address
    String RefDesc1 = "";
    // Note lines that are not the alternate address
    String RefDesc2 = "";
    // Note lines that are not the alternate address
    String RefDesc3 = "";
    // Note lines that are not the alternate address
    String RefDesc4 = "";
    String FirstNoteAfterAddressInfo = "";
    String SecondNoteAfterAddressInfo = "";
    String ThirdNoteAfterAddressInfo = "";
    String Ref1Qualifier = "";
    String Ref2Qualifier = "";
    String Ref3Qualifier = "";
    String Ref4Qualifier = "";
    String dateQualifier = "";
    // Filename for the Fast Track file generated by this method
    String ftFilename = "";
    // Filename for the issuance (or account reconciliation) file generated by this method for immediate payments
    String arFilename = "";
    // The total number of "add" issues (see record #6)
    int arNumOfAddIssues = 0;
    // The total number of issuance records
    int numOfIssuanceRecords = 0;
    // The total dollar amount of the add issues for the issuance file.
    KualiDecimal arTotalOfAddIssues = KualiDecimal.ZERO;
    boolean wroteMellonIssuanceHeaderRecords = false;
    boolean wroteMellonFastTrackHeaderRecords = false;
    CustomerProfile cp = null;
    String sCountryName = "";
    String CheckNumber = "";
    boolean MissingCommaFromSpecialHandlingAddress = false;
    int totalRecordCount = 0;
    KualiDecimal totalPaymentAmounts = KualiDecimal.ZERO;
    try {
        // Establish whether we are in a TEST or PRODUCTION environment.  This will change the indicator in the Mellon header record
        if (isProduction())
            testIndicator = "P";
        else
            testIndicator = "T";
        // Change the filename so that it ends in .txt instead of .xml
        ftFilename = filename.replace(".xml", ".txt");
        arFilename = ftFilename.replace("check", "check_immediate");
        // Obtain the bank account number from the check information provided
        List<String> bankCodes1 = paymentGroupService.getDistinctBankCodesForProcessAndType(processId, PdpConstants.DisbursementTypeCodes.CHECK);
        if (!bankCodes1.isEmpty()) {
            List<Integer> disbNbrs1 = paymentGroupService.getDisbursementNumbersByDisbursementTypeAndBankCode(processId, PdpConstants.DisbursementTypeCodes.CHECK, bankCodes1.get(0));
            if (!disbNbrs1.isEmpty()) {
                Iterator<PaymentDetail> myPds = paymentDetailService.getByDisbursementNumber(disbNbrs1.get(0));
                if (myPds.hasNext()) {
                    PaymentDetail myPd = myPds.next();
                    PaymentGroup myPg = myPd.getPaymentGroup();
                    if (ObjectUtils.isNotNull(myPg)) {
                        ourBankAccountNumber = myPg.getBank().getBankAccountNumber().replace("-", "");
                        ourBankRoutingNumber = myPg.getBank().getBankRoutingNumber();
                    } else {
                        LOG.error("No Payment group information exists for requisition number: " + myPd.getRequisitionNbr() + ".  Payee name is: " + myPg.getPayeeName());
                        throw new Exception("No Payment group information exists for requisition number: " + myPd.getRequisitionNbr() + ".  Payee name is: " + myPg.getPayeeName());
                    }
                }
            }
        }
        // At this point we will start looping through all the checks and write the PAY01000 record (one for each payee),
        // two (2) PDT2010 records (one for the Payer & Payee) and as many REM3020 records as needed for each amount being
        // paid to this payee.
        List<String> bankCodes = paymentGroupService.getDistinctBankCodesForProcessAndType(processId, PdpConstants.DisbursementTypeCodes.CHECK);
        for (String bankCode : bankCodes) {
            List<Integer> disbNbrs = paymentGroupService.getDisbursementNumbersByDisbursementTypeAndBankCode(processId, PdpConstants.DisbursementTypeCodes.CHECK, bankCode);
            for (Iterator<Integer> iter = disbNbrs.iterator(); iter.hasNext(); ) {
                Integer disbursementNbr = iter.next();
                // If this payee has multiple checks coming to them, this ensures we only generate 1 PAY01000 record
                boolean first = true;
                KualiDecimal totalNetAmount = new KualiDecimal(0);
                // We continue to need this for the FastTrack file as well since the total net amount is needed on the PAY01000 record for each payee
                Iterator<PaymentDetail> i2 = paymentDetailService.getByDisbursementNumber(disbursementNbr, processId, PdpConstants.DisbursementTypeCodes.CHECK, bankCode);
                while (i2.hasNext()) {
                    PaymentDetail pd = i2.next();
                    totalNetAmount = totalNetAmount.add(pd.getNetPaymentAmount());
                }
                Iterator<PaymentDetail> paymentDetails = paymentDetailService.getByDisbursementNumber(disbursementNbr, processId, PdpConstants.DisbursementTypeCodes.CHECK, bankCode);
                while (paymentDetails.hasNext()) {
                    PaymentDetail pd = paymentDetails.next();
                    PaymentGroup pg = pd.getPaymentGroup();
                    // We save these values to the DB AFTER we know that this check has passed all validation and has actually been written
                    pg.setDisbursementDate(new java.sql.Date(processDate.getTime()));
                    pg.setPaymentStatus(extractedStatus);
                    // Get immediate (a.k.a. local print, a.k.a. manual) check indicator
                    immediateCheckCode = pg.getProcessImmediate();
                    // If it exists, get the check (a.k.a. disbursement number)
                    CheckNumber = (ObjectUtils.isNotNull(pg.getDisbursementNbr()) ? pg.getDisbursementNbr().toString() : "");
                    // Parse Notes
                    Iterator<PaymentNoteText> ix = pd.getNotes().iterator();
                    NumOfAltAddressLines = 0;
                    altAddrSendTo = "";
                    altAddrAddr1 = "";
                    altAddrAddr2 = "";
                    altAddrAddr3 = "";
                    altAddrAddr4 = "";
                    altAddrCity = "";
                    altAddrState = "";
                    altAddrZip = "";
                    altCountryName = "";
                    altAddrCityStateZip = "";
                    PreparerInfoText = "";
                    SendToPrefLength = 0;
                    FirstNoteAfterAddressInfo = "";
                    SecondNoteAfterAddressInfo = "";
                    ThirdNoteAfterAddressInfo = "";
                    MissingCommaFromSpecialHandlingAddress = false;
                    while (ix.hasNext()) {
                        PaymentNoteText note = (PaymentNoteText) ix.next();
                        String NoteLine = note.getCustomerNoteText();
                        if (NoteLine.contains(CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_PREPARER)) {
                            SendToPrefLength = CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_NAME.length();
                            PreparerInfoText = NoteLine;
                        } else if (NoteLine.contains(CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_NAME)) {
                            SendToPrefLength = CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_NAME.length();
                            altAddrSendTo = NoteLine.substring(SendToPrefLength);
                            NumOfAltAddressLines = NumOfAltAddressLines + 1;
                        } else if (NoteLine.contains(CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS1)) {
                            SendToPrefLength = CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS1.length();
                            altAddrAddr1 = NoteLine.substring(SendToPrefLength);
                            NumOfAltAddressLines = NumOfAltAddressLines + 1;
                        } else if (NoteLine.contains(CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS2)) {
                            SendToPrefLength = CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS2.length();
                            altAddrAddr2 = NoteLine.substring(SendToPrefLength);
                            NumOfAltAddressLines = NumOfAltAddressLines + 1;
                        } else if (NoteLine.contains(CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS3)) {
                            SendToPrefLength = CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS3.length();
                            if (NoteLine.contains(",")) {
                                altAddrCityStateZip = NoteLine.substring(SendToPrefLength);
                                // sTemp will be the string we modify as we go along
                                String sTemp = altAddrCityStateZip;
                                int spaceForZip = 0;
                                spaceForZip = sTemp.lastIndexOf(" ");
                                if (spaceForZip == -1) {
                                    // ZIP is missing so set it to "" per discussion with Marcia
                                    altAddrZip = "";
                                    LOG.warn("WARNING: No zip code was provided.  Changing it to blanks for check number: " + CheckNumber);
                                } else {
                                    if (sTemp.substring(spaceForZip).trim().toLowerCase().equals("null")) {
                                        // Use the space between the state and zip to obtain the "null" zip
                                        altAddrZip = sTemp.substring(spaceForZip);
                                        // Remove the "null" zip characters from sTemp
                                        sTemp = sTemp.replace(altAddrZip, "");
                                        // Since ZIP is missing set it to "" per discussion with Marcia
                                        altAddrZip = "";
                                        LOG.warn("WARNING: No zip code was provided.  Changing it to blanks for check number: " + CheckNumber);
                                    } else {
                                        // Use the space between the state and zip to obtain the zip
                                        altAddrZip = sTemp.substring(spaceForZip);
                                        // Remove the space + Zip characters from sTemp
                                        sTemp = sTemp.replace(altAddrZip, "");
                                        // Trim any leading or trailing blanks
                                        altAddrZip = altAddrZip.trim();
                                    }
                                }
                                // Use the comma to find the where the state starts
                                altAddrState = sTemp.substring(sTemp.lastIndexOf(",") + 1);
                                // Remove what we found from sTemp
                                sTemp = sTemp.replace(altAddrState, "");
                                // Trim any leading or trailing blanks
                                altAddrState = altAddrState.trim();
                                // There should only be one comma.  If commas are in the city name, that will be an issue
                                altAddrCity = sTemp.replace(",", "");
                                // Trim any leading or trailing blanks.
                                altAddrCity = altAddrCity.trim();
                                NumOfAltAddressLines = NumOfAltAddressLines + 1;
                            } else {
                                // We can't find a comma, so we won't know where the city ends and the state begins so as per discussion,
                                // log it as a warning and move on to the next NoteLine because we may have notes that we want to process.
                                LOG.warn("WARNING: No comma was provided separating the city and state for check number: " + CheckNumber);
                                MissingCommaFromSpecialHandlingAddress = true;
                                continue;
                            }
                        } else // if ( NoteLine.contains(CuDisbursementVoucherConstants.DV_EXTRACT_NOTE_PREFIX_SPECIAL_HANDLING_ADDRESS3) )
                        // Retrieve up to 3 subsequent note lines and only the first 72 characters as per the BNY Mellon spec.
                        {
                            // the next line until we either run out of lines or reach the max number to take (which is 3).
                            if (NoteLine.length() >= 2) {
                                if (NoteLine.substring(0, 2).contains(CuDisbursementVoucherConstants.DV_EXTRACT_TYPED_NOTE_PREFIX_IDENTIFIER)) {
                                    // Trim the first two characters from the note and assign it as the first user typed note line
                                    NoteLine = NoteLine.substring(2);
                                    FirstNoteAfterAddressInfo = (NoteLine.length() <= 72) ? NoteLine : NoteLine.substring(0, 72);
                                    // See if we have a second user typed note line.  If so, then get it.
                                    if (ix.hasNext()) {
                                        note = (PaymentNoteText) ix.next();
                                        NoteLine = note.getCustomerNoteText();
                                        if (NoteLine.length() >= 2) {
                                            if (NoteLine.substring(0, 2).contains(CuDisbursementVoucherConstants.DV_EXTRACT_TYPED_NOTE_PREFIX_IDENTIFIER)) {
                                                NoteLine = NoteLine.substring(2);
                                                SecondNoteAfterAddressInfo = (NoteLine.length() <= 72) ? NoteLine : NoteLine.substring(0, 72);
                                                // Try to get the third user typed note line
                                                if (ix.hasNext()) {
                                                    note = (PaymentNoteText) ix.next();
                                                    NoteLine = note.getCustomerNoteText();
                                                    if (NoteLine.length() >= 2) {
                                                        if (NoteLine.substring(0, 2).contains(CuDisbursementVoucherConstants.DV_EXTRACT_TYPED_NOTE_PREFIX_IDENTIFIER)) {
                                                            NoteLine = NoteLine.substring(2);
                                                            ThirdNoteAfterAddressInfo = (NoteLine.length() <= 72) ? NoteLine : NoteLine.substring(0, 72);
                                                            // Break here because the Mellon spec only allows us to use the first three user typed note lines
                                                            break;
                                                        } else
                                                            // Since we're on our potentially last note if this isn't a user types note, then we're done with the while loop
                                                            break;
                                                    } else
                                                        // Since this is the last potential user note and it doesn't contain :: break out of the while loop
                                                        break;
                                                } else
                                                    // Out of all notes, so break out of the while loop
                                                    break;
                                            } else
                                                // Getting here means that we ran into a note line that doesn't have the :: in front.  Continuing will over write the first notes we did capture, so
                                                break;
                                        } else
                                            // Interspersed system generated notes can't occur (yet) so break out since we've already processed 1 user entered note.
                                            break;
                                    } else
                                        // We've processed the first user entered note, but now we find a system generated note, so break out since this would mean that user notes are done as they are contiguous
                                        break;
                                } else
                                    // Getting here means this is not a user note and since we haven't found the first user note, keep looking
                                    continue;
                            } else
                                // Getting here means this is not a user note, so since we still haven't found the first user note keep on looking.
                                continue;
                        }
                    // else  (user notes section of this code)
                    }
                    if (MissingCommaFromSpecialHandlingAddress)
                        break;
                    // Get customer profile information
                    if (ObjectUtils.isNotNull(pg.getBatch())) {
                        cp = pg.getBatch().getCustomerProfile();
                        if (ObjectUtils.isNotNull(cp))
                            if (ObjectUtils.isNotNull(cp.getSubUnitCode()))
                                subUnitCode = cp.getSubUnitCode();
                            else {
                                LOG.error("No Sub Unit Code provided for requisition number: " + pd.getRequisitionNbr());
                                break;
                            }
                        else {
                            LOG.error("No customer profile exists for payee name: " + pg.getPayeeName());
                            break;
                        }
                    }
                    if (first && !immediateCheckCode) {
                        if (!wroteMellonFastTrackHeaderRecords) {
                            // Open the file
                            os = new BufferedWriter(new FileWriter(ftFilename));
                            // Write the Fast Track header record (FIL00010)
                            os.write(// Record Type
                            "FIL00010" + cDelim + hdrRecType + // Variable (V) or Fixed (F) flag
                            cDelim + cDname + // Delimiter name - Must be either FFCARET or FFCOMMA.  Others are allowed but BNY Mellon will have to be contacted first.
                            cDelim + "CORNELLUNIVKFS" + // Customer Id - a unique identifier for the customer
                            cDelim + testIndicator + // Test Indicator:  T = Test run, P = Production Run
                            cDelim + "820" + // EDI Document Id (3 Bytes)
                            cDelim + "043000261" + // Our Mellon bank id (15 Bytes)
                            cDelim + // Customer Division Id - 35 bytes - Optional
                            cDelim + sdf.format(processDate) + // File Date and Time - 14 Bytes
                            cDelim + // Reserved Field - 3 Bytes
                            cDelim + // Filler - 872 bytes
                            "\n");
                            totalRecordCount = 1;
                            // Write the Fast Track email records (FIL00020) once for each file
                            for (Iterator<String> emailIter = notificationEmailAddresses.iterator(); emailIter.hasNext(); ) {
                                String emailAddress = emailIter.next();
                                // write (FIL00020) record for each email address
                                os.write("FIL00020" + cDelim + emailAddress + cDelim + cDelim + "\n");
                                totalRecordCount = totalRecordCount + 1;
                            }
                            wroteMellonFastTrackHeaderRecords = true;
                        }
                        // Get country name
                        int CountryNameMaxLength = 15;
                        Country country = null;
                        if (StringUtils.isNotBlank(pg.getCountry())) {
                            country = countryService.getCountry(pg.getCountry());
                        }
                        if (country != null)
                            sCountryName = country.getName().substring(0, ((country.getName().length() >= CountryNameMaxLength) ? CountryNameMaxLength : country.getName().length()));
                        else if (ObjectUtils.isNotNull(pg.getCountry()))
                            sCountryName = pg.getCountry().substring(0, ((pg.getCountry().length() >= CountryNameMaxLength) ? CountryNameMaxLength : pg.getCountry().length()));
                        else
                            // Do this only if both the getPostalCountryName() AND getCountry() are empty
                            sCountryName = "";
                        // Do final country name processing per requirements from BNY Mellon.
                        if (sCountryName.toUpperCase().contains("UNITED STATES"))
                            sCountryName = "";
                        // Get special handling indicator
                        specialHandlingCode = pg.getPymtSpecialHandling();
                        // Get attachment indicator
                        attachmentCode = pg.getPymtAttachment();
                        // Determines the division code based on special handling indicator and attachment indicator
                        int dvCodeInt = 0;
                        if (specialHandlingCode == false && attachmentCode == false) {
                            dvCodeInt = CUPdpConstants.DivisionCodes.US_MAIL;
                        }
                        if (specialHandlingCode == true && attachmentCode == false) {
                            dvCodeInt = CUPdpConstants.DivisionCodes.US_MAIL;
                        }
                        if (specialHandlingCode == false && attachmentCode == true) {
                            dvCodeInt = CUPdpConstants.DivisionCodes.CU_MAIL_SERVICES;
                        }
                        if (specialHandlingCode == true && attachmentCode == true) {
                            dvCodeInt = CUPdpConstants.DivisionCodes.CU_MAIL_SERVICES;
                        }
                        divisionCode = String.format(String.format("%%0%dd", 3), dvCodeInt);
                        Date DisbursementDate;
                        if (ObjectUtils.isNotNull(pg.getDisbursementDate()))
                            DisbursementDate = pg.getDisbursementDate();
                        else {
                            LOG.error("Disbursement Date is NULL for Disbursement Number: " + CheckNumber);
                            break;
                        }
                        // Write the Fast Track PAY01000 record (only one per payee)
                        os.write(// Record Type - 8 bytes
                        "PAY01000" + cDelim + "7" + // 7=Payment and Electronic Advice (Transaction handling code - 2 bytes)
                        cDelim + totalNetAmount.toString() + // Total amount of check (Payment amount - 18 bytes)
                        cDelim + "C" + // C=Credit, D=Debit (Credit or debit Flag - 1 Byte)
                        cDelim + "CHK" + // CHK=Check (Payment method - 3 Bytes)
                        cDelim + "PBC" + // PBC is used for checks (Payment Format - 10 bytes)
                        cDelim + "01" + // Originators bank id qualifier - 2 bytes
                        cDelim + ourBankRoutingNumber + // Originators bank id - 12 bytes
                        cDelim + "DA" + // Originating account number Qualifier - 3 bytes
                        cDelim + ourBankAccountNumber + // Originating account number - 35 bytes
                        cDelim + "2150532082" + // Originating company identifier - 10 bytes  This has to be different for this file than it is for ACH payments per BNY Mellon
                        cDelim + // Receiving bank id qualifier - 2 bytes
                        cDelim + // Receiving bank id - 12 bytes
                        cDelim + // Receiving account number qualifier - 3 bytes
                        cDelim + // Receiving account number - 35 bytes
                        cDelim + sdfPAY1000Rec.format(DisbursementDate) + // Effective date - 8 bytes - YYMMDD format
                        cDelim + // Business function code - 3 bytes
                        cDelim + CheckNumber + // Trace number (check number) - 50 bytes
                        cDelim + divisionCode + // Division code - 50 bytes
                        cDelim + // Currency code - 3 bytes
                        cDelim + // Note 1 - 80 bytes
                        cDelim + // Note 2 - 80 bytes
                        cDelim + // Note 3 - 80 bytes
                        cDelim + // Note 4 - 80 bytes
                        cDelim + // Filler
                        cDelim + "\n");
                        totalPaymentAmounts = totalPaymentAmounts.add(totalNetAmount);
                        // Write the Fast Track Payer Detail(PDT02010) record (only one per payee)
                        os.write(// Record Type - 8 bytes
                        "PDT02010" + cDelim + "PR" + // Name qualifier - 3 bytes (PR = Payer)
                        cDelim + // ID code qualifier - 2 bytes
                        cDelim + // ID code - 80 bytes
                        cDelim + PAYER_NAME + // Name - 35 bytes
                        cDelim + // Additional name 1 - 60 bytes
                        cDelim + // Additional name 2 - 60 bytes
                        cDelim + PAYER_ADDRESS_LINE1 + // Address line 1 - 35 bytes
                        cDelim + PAYER_ADDRESS_LINE2 + // Address line 2 - 35 bytes
                        cDelim + // Address line 3 - 35 bytes
                        cDelim + // Address line 4 - 35 bytes
                        cDelim + // Address line 5 - 35 bytes
                        cDelim + // Address line 6 - 35 bytes
                        cDelim + PAYER_CITY + // City - 30 bytes
                        cDelim + PAYER_STATE + // State/Province - 2 bytes
                        cDelim + PAYER_ZIP_CODE + // Postal code - 15 bytes
                        cDelim + // Country code - 3 bytes
                        cDelim + // Country name - 30 bytes
                        cDelim + // Ref qualifier 1 - 3 bytes
                        cDelim + // Ref ID 1 - 50 bytes
                        cDelim + // Ref description 1 - 80 bytes
                        cDelim + // Ref qualifier 1 - 3 bytes
                        cDelim + // Ref ID 1 - 50 bytes
                        cDelim + // Ref description 1 - 80 bytes
                        cDelim + cDelim + "\n");
                        // Write the Fast Track initial payee detail (PDT02010) record
                        int AddrMaxLength = 35;
                        int CityMaxLength = 30;
                        int StateMaxLength = 2;
                        int ZipMaxLength = 15;
                        int PayeeNameMaxLength = 35;
                        int PayeeIdMaxLength = 18;
                        String PayeeName = "";
                        String PayeeId = "";
                        String PayeeIdQualifier = "";
                        String AddrLine1 = "";
                        String AddrLine2 = "";
                        String AddrLine3 = "";
                        String AddrLine4 = "";
                        String AddrCity = "";
                        String AddrState = "";
                        String AddrZip = "";
                        if (ObjectUtils.isNotNull(pg.getPayeeName()))
                            PayeeName = pg.getPayeeName().substring(0, ((pg.getPayeeName().length() >= PayeeNameMaxLength) ? PayeeNameMaxLength : pg.getPayeeName().length()));
                        if (ObjectUtils.isNotNull(pg.getPayeeId())) {
                            PayeeId = pg.getPayeeId().substring(0, ((pg.getPayeeId().length() >= PayeeIdMaxLength) ? PayeeIdMaxLength : pg.getPayeeId().length()));
                            PayeeIdQualifier = "ZZ";
                        }
                        if (ObjectUtils.isNotNull(pg.getLine1Address()))
                            AddrLine1 = pg.getLine1Address().substring(0, ((pg.getLine1Address().length() >= AddrMaxLength) ? AddrMaxLength : pg.getLine1Address().length()));
                        if (ObjectUtils.isNotNull(pg.getLine2Address()))
                            AddrLine2 = pg.getLine2Address().substring(0, ((pg.getLine2Address().length() >= AddrMaxLength) ? AddrMaxLength : pg.getLine2Address().length()));
                        if (ObjectUtils.isNotNull(pg.getLine3Address()))
                            AddrLine3 = pg.getLine3Address().substring(0, ((pg.getLine3Address().length() >= AddrMaxLength) ? AddrMaxLength : pg.getLine3Address().length()));
                        if (ObjectUtils.isNotNull(pg.getLine4Address()))
                            AddrLine4 = pg.getLine4Address().substring(0, ((pg.getLine4Address().length() >= AddrMaxLength) ? AddrMaxLength : pg.getLine4Address().length()));
                        if (ObjectUtils.isNotNull(pg.getCity()))
                            AddrCity = pg.getCity().substring(0, ((pg.getCity().length() >= CityMaxLength) ? CityMaxLength : pg.getCity().length()));
                        if (ObjectUtils.isNotNull(pg.getState()))
                            AddrState = pg.getState().substring(0, ((pg.getState().length() >= StateMaxLength) ? StateMaxLength : pg.getState().length()));
                        if (ObjectUtils.isNotNull(pg.getZipCd()))
                            AddrZip = (pg.getZipCd().substring(0, ((pg.getZipCd().length() >= ZipMaxLength) ? ZipMaxLength : pg.getZipCd().length()))).replace("-", "");
                        os.write(// Record Type - 8 bytes
                        "PDT02010" + cDelim + "PE" + // Name qualifier - 3 bytes (PE = Payee) This record's data prints on the check
                        cDelim + PayeeIdQualifier + // ID code qualifier - 2 bytes
                        cDelim + PayeeId + // ID code - 80 bytes
                        cDelim + PayeeName + // Name - 35 bytes
                        cDelim + // Additional name 1 - 60 bytes
                        cDelim + // Additional name 2 - 60 bytes
                        cDelim + AddrLine1 + // Address line 1 - 35 bytes
                        cDelim + AddrLine2 + // Address line 2 - 35 bytes
                        cDelim + AddrLine3 + // Address line 3 - 35 bytes
                        cDelim + AddrLine4 + // Address line 4 - 35 bytes
                        cDelim + // Address line 5 - 35 bytes
                        cDelim + // Address line 6 - 35 bytes
                        cDelim + AddrCity + // City - 30 bytes
                        cDelim + AddrState + // State/Province - 2 bytes
                        cDelim + AddrZip + // Postal code - 15 bytes
                        cDelim + // Country code - 3 bytes
                        cDelim + sCountryName + // Country name - 15 bytes (do not use if Country Code is used)
                        cDelim + // Ref qualifier 1 - 3 bytes (not used)
                        cDelim + // Ref ID 1 - 50 bytes (not used)
                        cDelim + // Ref description 1 - 80 bytes (not used)
                        cDelim + // Ref qualifier 1 - 3 bytes (not used)
                        cDelim + // Ref ID 1 - 50 bytes (not used)
                        cDelim + // Ref description 1 - 80 bytes (not used)
                        cDelim + cDelim + "\n");
                        // If no alternate address is provided, we must populate with the customer address
                        if (NumOfAltAddressLines == 0) {
                            // If we were not provided an alternate address to mail the check to, then assign the same values
                            // in the third PDT02010 record the same exact values as the second PDT02010 record.
                            altAddrSendTo = PayeeName;
                            altAddrAddr1 = AddrLine1;
                            altAddrAddr2 = AddrLine2;
                            altAddrAddr3 = AddrLine3;
                            altAddrAddr4 = AddrLine4;
                            altAddrCity = AddrCity;
                            altAddrState = AddrState;
                            altAddrZip = AddrZip;
                            altAddrCityStateZip = altAddrCity + ", " + altAddrState + " " + altAddrZip;
                            altCountryName = sCountryName;
                            altRefQualifer = "ZZ";
                        } else {
                            altAddrZip = altAddrZip.replace("-", "");
                            // If we have two addresses, an original and an alternate, make sure the alternate does not inherit the original's country name!
                            altCountryName = "";
                        }
                        // Write the Fast Track second payee detail (PDT02010) record (for alternate addressing (special handling addressing case)
                        os.write(// Record Type - 8 bytes
                        "PDT02010" + cDelim + "FE" + // Name qualifier - 3 bytes (FE = Remit) This record's data prints on the remittance
                        cDelim + PayeeIdQualifier + // ID code qualifier - 2 bytes
                        cDelim + PayeeId + // ID code - 80 bytes
                        cDelim + altAddrSendTo + // Name - 35 bytes
                        cDelim + // Additional name 1 - 60 bytes
                        cDelim + // Additional name 2 - 60 bytes
                        cDelim + altAddrAddr1 + // Address line 1 - 35 bytes
                        cDelim + altAddrAddr2 + // Address line 2 - 35 bytes
                        cDelim + altAddrAddr3 + // Address line 3 - 35 bytes
                        cDelim + altAddrAddr4 + // Address line 4 - 35 bytes
                        cDelim + // Address line 5 - 35 bytes
                        cDelim + // Address line 6 - 35 bytes
                        cDelim + altAddrCity + // City - 30 bytes
                        cDelim + altAddrState + // State/Province - 2 bytes
                        cDelim + altAddrZip + // Postal code - 15 bytes
                        cDelim + // Country code - 3 bytes
                        cDelim + altCountryName + // Country name - 30 15 bytes (do not use if Country Code is used)  Alternate addresses are never outside the USA.
                        cDelim + // Ref qualifier 1 - 3 bytes
                        cDelim + // Ref ID 1 - 50 bytes
                        cDelim + // Ref description 1 - 80 bytes
                        cDelim + // Ref qualifier 1 - 3 bytes
                        cDelim + // Ref ID 1 - 50 bytes
                        cDelim + // Ref description 1 - 80 bytes
                        cDelim + cDelim + "\n");
                        // One for the PAY01000 record and three for the PDT02010 records
                        totalRecordCount = totalRecordCount + 4;
                        // Set this here so it is only executed once per payee
                        first = false;
                    }
                    // if (first && !immediateCheckCode)
                    // Write the Fast Track REM03020 records
                    // Up to 3 characters
                    String remittanceIdCode = "";
                    // Up to 22 characters
                    String remittanceIdText = "";
                    // Here we will NOT have an invoice number but we will have an eDoc number and NO PO number
                    if (subUnitCode.equals(CuDisbursementVoucherConstants.DV_EXTRACT_SUB_UNIT_CODE)) {
                        remittanceIdCode = "TN";
                        // Here, we are guaranteed to have a pd.getCustPaymentDocNbr
                        remittanceIdText = "Doc No:" + pd.getCustPaymentDocNbr();
                        // Assign RefDesc1
                        RefDesc1 = "";
                    } else // Here we will have an invoice number and an eDoc number and a PO number
                    if (subUnitCode.equals("PRAP")) {
                        remittanceIdCode = "IV";
                        // Here, we are guaranteed to have a pd.getInvoiceNbr
                        remittanceIdText = pd.getInvoiceNbr();
                        // Assign RefDesc1
                        if (ObjectUtils.isNotNull(pd.getPurchaseOrderNbr()))
                            RefDesc1 = "PO:" + pd.getPurchaseOrderNbr() + ", Doc No:" + pd.getCustPaymentDocNbr();
                        else
                            RefDesc1 = "Doc No:" + pd.getCustPaymentDocNbr();
                    } else // Here we will have an invoice number and an eDoc number but we will NOT have PO number
                    if (!subUnitCode.equals(CuDisbursementVoucherConstants.DV_EXTRACT_SUB_UNIT_CODE) && !subUnitCode.equals("PRAP")) {
                        remittanceIdCode = "IV";
                        // Here, we are guaranteed to have a pd.getInvoiceNbr
                        remittanceIdText = pd.getInvoiceNbr();
                        // Assign RefDesc1
                        RefDesc1 = "Doc No:" + pd.getCustPaymentDocNbr();
                    }
                    // Assign the RefDesc fields.
                    if (!PreparerInfoText.isEmpty())
                        if (RefDesc1.isEmpty()) {
                            if (FirstNoteAfterAddressInfo.isEmpty()) {
                                if (SecondNoteAfterAddressInfo.isEmpty()) {
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // no notes
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = "";
                                        RefDesc3 = "";
                                        RefDesc4 = "";
                                    } else {
                                        // Note3
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = ThirdNoteAfterAddressInfo;
                                        RefDesc3 = "";
                                        RefDesc4 = "";
                                    }
                                } else {
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // Note2
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = SecondNoteAfterAddressInfo;
                                        RefDesc3 = "";
                                        RefDesc4 = "";
                                    } else {
                                        // Note2, Note3
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = SecondNoteAfterAddressInfo;
                                        RefDesc3 = ThirdNoteAfterAddressInfo;
                                        RefDesc4 = "";
                                    }
                                }
                            } else {
                                // Note1
                                if (SecondNoteAfterAddressInfo.isEmpty()) {
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // Note1
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = FirstNoteAfterAddressInfo;
                                        RefDesc3 = "";
                                        RefDesc4 = "";
                                    } else {
                                        // Note1, Note3
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = FirstNoteAfterAddressInfo;
                                        RefDesc3 = ThirdNoteAfterAddressInfo;
                                        RefDesc4 = "";
                                    }
                                } else {
                                    // Note2
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // Note1, Note2
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = FirstNoteAfterAddressInfo;
                                        RefDesc3 = SecondNoteAfterAddressInfo;
                                        RefDesc4 = "";
                                    } else {
                                        // Note1, Note2, Note3
                                        RefDesc1 = PreparerInfoText;
                                        RefDesc2 = FirstNoteAfterAddressInfo;
                                        RefDesc3 = SecondNoteAfterAddressInfo;
                                        RefDesc4 = ThirdNoteAfterAddressInfo;
                                    }
                                }
                            }
                        } else {
                            // RefDesc1 contains text
                            if (FirstNoteAfterAddressInfo.isEmpty()) {
                                if (SecondNoteAfterAddressInfo.isEmpty()) {
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // no notes
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = "";
                                        RefDesc4 = "";
                                    } else {
                                        // Note3
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = ThirdNoteAfterAddressInfo;
                                        RefDesc4 = "";
                                    }
                                } else {
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // Note2
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = SecondNoteAfterAddressInfo;
                                        RefDesc4 = "";
                                    } else {
                                        // Note2, Note3
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = SecondNoteAfterAddressInfo;
                                        RefDesc4 = ThirdNoteAfterAddressInfo;
                                    }
                                }
                            } else {
                                // Note1
                                if (SecondNoteAfterAddressInfo.isEmpty()) {
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // Note1
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = FirstNoteAfterAddressInfo;
                                        RefDesc4 = "";
                                    } else {
                                        // Note1, Note3
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = FirstNoteAfterAddressInfo;
                                        RefDesc4 = ThirdNoteAfterAddressInfo;
                                    }
                                } else {
                                    // Note2
                                    if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                        // Note1, Note2
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = FirstNoteAfterAddressInfo;
                                        RefDesc4 = SecondNoteAfterAddressInfo;
                                    } else {
                                        // Note1, Note2, Note3
                                        RefDesc2 = PreparerInfoText;
                                        RefDesc3 = FirstNoteAfterAddressInfo;
                                        RefDesc4 = SecondNoteAfterAddressInfo;
                                    }
                                }
                            }
                        }
                    else // PreparerInfoText is empty
                    if (RefDesc1.isEmpty())
                        if (FirstNoteAfterAddressInfo.isEmpty()) {
                            if (SecondNoteAfterAddressInfo.isEmpty()) {
                                if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                    // no notes
                                    RefDesc1 = "";
                                    RefDesc2 = "";
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                } else {
                                    // Note3
                                    RefDesc1 = ThirdNoteAfterAddressInfo;
                                    RefDesc2 = "";
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                }
                            } else {
                                if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                    // Note2
                                    RefDesc1 = SecondNoteAfterAddressInfo;
                                    RefDesc2 = "";
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                } else {
                                    // Note2, Note3
                                    RefDesc1 = SecondNoteAfterAddressInfo;
                                    RefDesc2 = ThirdNoteAfterAddressInfo;
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                }
                            }
                        } else {
                            // Note1
                            if (SecondNoteAfterAddressInfo.isEmpty()) {
                                if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                    // Note1
                                    RefDesc1 = FirstNoteAfterAddressInfo;
                                    RefDesc2 = "";
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                } else {
                                    // Note1, Note3
                                    RefDesc1 = FirstNoteAfterAddressInfo;
                                    RefDesc2 = ThirdNoteAfterAddressInfo;
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                }
                            } else {
                                // Note2
                                if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                    // Note1, Note2
                                    RefDesc1 = FirstNoteAfterAddressInfo;
                                    RefDesc2 = SecondNoteAfterAddressInfo;
                                    RefDesc3 = "";
                                    RefDesc4 = "";
                                } else {
                                    // Note1, Note2, Note3
                                    RefDesc1 = FirstNoteAfterAddressInfo;
                                    RefDesc2 = SecondNoteAfterAddressInfo;
                                    RefDesc3 = ThirdNoteAfterAddressInfo;
                                    RefDesc4 = "";
                                }
                            }
                        }
                    else // RefDesc1 contains text
                    if (FirstNoteAfterAddressInfo.isEmpty()) {
                        if (SecondNoteAfterAddressInfo.isEmpty()) {
                            if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                // no notes
                                RefDesc2 = "";
                                RefDesc3 = "";
                                RefDesc4 = "";
                            } else {
                                // Note3
                                RefDesc2 = ThirdNoteAfterAddressInfo;
                                RefDesc3 = "";
                                RefDesc4 = "";
                            }
                        } else {
                            if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                // Note2
                                RefDesc2 = SecondNoteAfterAddressInfo;
                                RefDesc3 = "";
                                RefDesc4 = "";
                            } else {
                                // Note2, Note3
                                RefDesc2 = SecondNoteAfterAddressInfo;
                                RefDesc3 = ThirdNoteAfterAddressInfo;
                                RefDesc4 = "";
                            }
                        }
                    } else {
                        // Note1
                        if (SecondNoteAfterAddressInfo.isEmpty()) {
                            if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                // Note1
                                RefDesc2 = FirstNoteAfterAddressInfo;
                                RefDesc3 = "";
                                RefDesc4 = "";
                            } else {
                                // Note1, Note3
                                RefDesc2 = FirstNoteAfterAddressInfo;
                                RefDesc3 = ThirdNoteAfterAddressInfo;
                                RefDesc4 = "";
                            }
                        } else {
                            // Note2
                            if (ThirdNoteAfterAddressInfo.isEmpty()) {
                                // Note1, Note2
                                RefDesc2 = FirstNoteAfterAddressInfo;
                                RefDesc3 = SecondNoteAfterAddressInfo;
                                RefDesc4 = "";
                            } else {
                                // Note1, Note2, Note3
                                RefDesc2 = FirstNoteAfterAddressInfo;
                                RefDesc3 = SecondNoteAfterAddressInfo;
                                RefDesc4 = ThirdNoteAfterAddressInfo;
                            }
                        }
                    }
                    // If we have something in RefDesc1 then RefQualifier must be "ZZ" according to Mellon's spec.
                    if (!RefDesc1.isEmpty())
                        Ref1Qualifier = "ZZ";
                    else
                        Ref1Qualifier = "";
                    // If we have something in RefDesc2 then RefQualifier must be "ZZ" according to Mellon's spec.
                    if (!RefDesc2.isEmpty())
                        Ref2Qualifier = "ZZ";
                    else
                        Ref2Qualifier = "";
                    // If we have something in RefDesc3 then RefQualifier must be "ZZ" according to Mellon's spec.
                    if (!RefDesc3.isEmpty())
                        Ref3Qualifier = "ZZ";
                    else
                        Ref3Qualifier = "";
                    // If we have something in RefDesc4 then RefQualifier must be "ZZ" according to Mellon's spec.
                    if (!RefDesc4.isEmpty())
                        Ref4Qualifier = "ZZ";
                    else
                        Ref4Qualifier = "";
                    // Only perform the following if this is an immediate check
                    if (immediateCheckCode) {
                        if (!wroteMellonIssuanceHeaderRecords) {
                            // Open the File
                            osI = new BufferedWriter(new FileWriter(arFilename));
                            // Write the Mellon issuance header record
                            osI.write(// 
                            "1" + // 
                            repeatThis(" ", 2) + // 
                            "MELLONBANK" + // 
                            "CORNELL   " + // 
                            sdfIDate.format(processDate) + // 
                            sdfITime.format(processDate) + repeatThis(" ", 209) + // 
                            "\n");
                            // Write the BNY Mellon issuance service record
                            osI.write(// 
                            "2" + // 
                            repeatThis(" ", 30) + // 
                            "100" + // 
                            "242" + // 
                            "0242" + // 
                            "1" + repeatThis(" ", 200) + // 
                            "\n");
                            // 2 issuance records added here.
                            numOfIssuanceRecords = numOfIssuanceRecords + 2;
                            wroteMellonIssuanceHeaderRecords = true;
                        }
                        String arPayeeName = "";
                        String arLine1Address = "";
                        if (ObjectUtils.isNotNull(pg.getPayeeName()))
                            arPayeeName = pg.getPayeeName();
                        if (ObjectUtils.isNotNull(pg.getLine1Address()))
                            arLine1Address = pg.getLine1Address();
                        // Write the BNY Mellon issuance detail (regular format) record
                        CheckNumber = repeatThis("0", 10 - pg.getDisbursementNbr().toString().length()) + pg.getDisbursementNbr().toString();
                        String AmountOfCheck = totalNetAmount.toString().replace(".", "");
                        AmountOfCheck = repeatThis("0", 10 - AmountOfCheck.length()) + AmountOfCheck;
                        osI.write(// Record Type  - 1 Byte
                        "6" + // Status Code->  2: Add Issue,  6: Void Issue   - 1 Byte
                        "2" + // Origin - Company Name - 10 Bytes
                        "CORNELL   " + // Destination - Identification of receiving location:  MELLONWEST, MELLONEAST, MELLONTRUS, BOSTONNOW - 10 bytes
                        "MELLONWEST" + repeatThis("0", 10 - ourBankAccountNumber.length()) + // Checking account number - 10 Bytes
                        ourBankAccountNumber + // Check Serial Number (check number) - 10 Bytes
                        CheckNumber + // Check Amount:  Format $$$$$$$$cc - 10 Bytes
                        AmountOfCheck + // Issue Date: Format YYMMDD - 6 Bytes
                        sdfIDate.format(processDate) + // Additional Data (Optional) - 10 bytes
                        repeatThis(" ", 10) + // Register Information (Optional) - 5 Bytes
                        repeatThis(" ", 5) + // Not used - 49 bytes
                        repeatThis(" ", 49) + // Payee Line 1 - Payee Name (Required) - 60 Bytes
                        String.format("%-60.60s", arPayeeName.toUpperCase()) + String.format("%-60.60s", arLine1Address.toUpperCase()) + // Payee Line 2 - Payee Name or first line of address (Required) - 60 Bytes
                        "\n");
                        // Totals the number of add issues across ALL checks issued not just for this one payee.
                        arNumOfAddIssues = arNumOfAddIssues + 1;
                        // Same as for the count but for the total dollar amount.
                        arTotalOfAddIssues = arTotalOfAddIssues.add(totalNetAmount);
                        // Add to the total number of issuance records
                        numOfIssuanceRecords = numOfIssuanceRecords + 1;
                    } else // if (immediateCheckCode)
                    {
                        // All of these are limited to 18 bytes in Fast Track.
                        String ftNetPayAmount = "";
                        String ftTotalAmount = "";
                        String ftDiscountAmt = "";
                        if (ObjectUtils.isNotNull(pd.getNetPaymentAmount())) {
                            ftNetPayAmount = pd.getNetPaymentAmount().toString();
                            if (ftNetPayAmount.length() > 18) {
                                LOG.error("Net Payment Amount is more than 18 bytes for check number " + CheckNumber);
                                break;
                            }
                        }
                        if (ObjectUtils.isNotNull(pd.getOrigInvoiceAmount())) {
                            ftTotalAmount = pd.getOrigInvoiceAmount().toString();
                            if (ftTotalAmount.length() > 18) {
                                LOG.error("Original Invoice Amount is more than 18 bytes for check number " + CheckNumber);
                                break;
                            }
                        }
                        if (ObjectUtils.isNotNull(pd.getInvTotDiscountAmount())) {
                            ftDiscountAmt = pd.getInvTotDiscountAmount().toString();
                            if (ftDiscountAmt.length() > 18) {
                                LOG.error("Discount Amount is more than 18 bytes for check number " + CheckNumber);
                                break;
                            }
                        }
                        String InvoiceDate = "";
                        if (ObjectUtils.isNotNull(pd.getInvoiceDate())) {
                            InvoiceDate = pd.getInvoiceDate().toString().replace("-", "");
                            dateQualifier = "003";
                        } else {
                            LOG.error("Invoice date is blank for check number " + CheckNumber);
                            break;
                        }
                        // Write the Fast Track REM03020 record
                        os.write(// Record type - 8 bytes
                        "REM03020" + cDelim + remittanceIdCode + // Remittance qualifier code - 3 bytes
                        cDelim + remittanceIdText + // Remittance ID - 22 bytes
                        cDelim + ftNetPayAmount + // Net invoice amount - 18 bytes
                        cDelim + ftTotalAmount + // Total invoice amount - 18 bytes
                        cDelim + ftDiscountAmt + // Discount amount - 18 bytes
                        cDelim + // Note 1 - 80 bytes
                        cDelim + // Note 2 - 80 bytes
                        cDelim + Ref1Qualifier + // Ref qualifier 1
                        cDelim + // Ref ID 1
                        cDelim + RefDesc1 + // Ref description 1 (up to 72 bytes)
                        cDelim + Ref2Qualifier + // Ref qualifier 2
                        cDelim + // Ref ID 2 (not used)
                        cDelim + RefDesc2 + // Ref description 2 (up to 72 bytes)
                        cDelim + Ref3Qualifier + // Ref qualifier 3
                        cDelim + // Ref ID 3 (not used)
                        cDelim + RefDesc3 + // Ref description 3 (up to 72 bytes)
                        cDelim + Ref4Qualifier + // Ref qualifier 4
                        cDelim + // Ref ID 4 (not used)
                        cDelim + RefDesc4 + // Ref description 4 (up to 72 bytes)
                        cDelim + dateQualifier + // Date qualifier 1
                        cDelim + InvoiceDate + // Date 1
                        cDelim + // Date qualifier 2 (not used)
                        cDelim + // Date 2 (not used)
                        cDelim + // Date qualifier 3 (not used)
                        cDelim + // Date 3 (not used)
                        cDelim + // Date qualifier 4 (not used)
                        cDelim + // Date 4 (not used)
                        cDelim + "\n");
                        // One for the REM03020 records
                        totalRecordCount = totalRecordCount + 1;
                    }
                    // Copied from the above XML generating method since this is the method that needs to record what was processed and what was not.
                    if (!testMode) {
                        pg.setDisbursementDate(new java.sql.Date(processDate.getTime()));
                        pg.setPaymentStatus(extractedStatus);
                        this.businessObjectService.save(pg);
                    }
                }
            // while (paymentDetails.hasNext())
            }
        // for (Iterator<Integer> iter = disbNbrs.iterator(); iter.hasNext();)
        }
        // for (String bankCode : bankCodes)
        // Need to update the total record count here to make sure it includes the trailer record
        totalRecordCount = totalRecordCount + 1;
        if (wroteMellonIssuanceHeaderRecords) {
            // Write the BNY Mellon issuance service total record
            String NumOfAddIssues = repeatThis("0", 10 - Integer.toString(arNumOfAddIssues).length()) + Integer.toString(arNumOfAddIssues);
            String TotalAmountOfAddIssues = arTotalOfAddIssues.toString().replace(".", "");
            TotalAmountOfAddIssues = repeatThis("0", 12 - TotalAmountOfAddIssues.length()) + TotalAmountOfAddIssues;
            osI.write(// Record Type
            "8" + // Total Number of Add Issues, 10 Bytes, numeric only, right justified, prefixed with zeros as needed to make length
            NumOfAddIssues + // Total Amount of Add Issues, 12 Bytes, numeric only (no decimals) right justified, prefixed with zeros as needed to make length
            TotalAmountOfAddIssues + // Total number of voided checks, 10 Bytes for voided checks which at this point we don't do.
            repeatThis("0", 10) + // Total amount of voided checks, 12 Bytes for the total voided amounts
            repeatThis("0", 12) + repeatThis(" ", 197) + // Required Filler
            "\n");
            // This is to account for record #8 above
            numOfIssuanceRecords = numOfIssuanceRecords + 1;
            // Write the BNY Mellon issuance trailer record
            // Need to add an issuance record here to account for the trailer record
            numOfIssuanceRecords = numOfIssuanceRecords + 1;
            String numberOfIssuanceRecords = repeatThis("0", 6 - Integer.toString(numOfIssuanceRecords).length()) + Integer.toString(numOfIssuanceRecords);
            osI.write(// Record Type
            "9" + // Total Number of Records, 6 bytes, numeric only, right justified, prefixed with zeros
            numberOfIssuanceRecords + repeatThis(" ", 235) + // Required Filler
            "\n");
        }
        if (wroteMellonFastTrackHeaderRecords) {
            // Fast Track trailer record
            os.write(// Record Type
            "TRL09000" + cDelim + totalRecordCount + // Total # of records in the file including header and trailer. 15 bytes numeric only
            cDelim + totalPaymentAmounts + // Total amount of all net payments for the file.  25 bytes numeric only
            cDelim + // EOR
            "\n");
        }
    }// try
     catch (IOException ie) {
        LOG.error("IO Exception with writeExtractCheckFileMellonBankFastTrack() Problem reading file:  " + ftFilename, ie);
        throw new IllegalArgumentException("Error writing to output file: " + ie.getMessage());
    } catch (Exception ex) {
        LOG.error("General Exception with writeExtractCheckFileMellonBankFastTrack().  Error is:  " + ex.getMessage(), ex);
    } finally {
        // Close file
        if (os != null) {
            try {
                os.close();
                // Need to do this at the end to indicate that the file is ready after it is closed.
                renameFile(ftFilename, ftFilename + ".READY");
            } catch (IOException ie) {
                // Not much we can do now
                LOG.error("IOException encountered in writeExtractCheckFileMellonBankFastTrack.  Message is: " + ie.getMessage());
            }
        }
        // osI.close();
        if (osI != null) {
            try {
                osI.close();
                // Rename the resulting file to have a .READY at the end
                // Need to do this at the end to indicate that the file is ready after it is closed.
                renameFile(arFilename, arFilename + ".READY");
            } catch (IOException ie) {
                // Not much we can do now
                LOG.error("IOException encountered in writeExtractAchFile.  Message is: " + ie.getMessage());
            }
        }
    }
}
Also used : FileWriter(java.io.FileWriter) BufferedWriter(java.io.BufferedWriter) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail) PaymentNoteText(org.kuali.kfs.pdp.businessobject.PaymentNoteText) KualiDecimal(org.kuali.rice.core.api.util.type.KualiDecimal) PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) CustomerProfile(org.kuali.kfs.pdp.businessobject.CustomerProfile) IOException(java.io.IOException) Date(java.util.Date) IOException(java.io.IOException) KualiInteger(org.kuali.rice.core.api.util.type.KualiInteger) Country(org.kuali.rice.location.api.country.Country) SimpleDateFormat(java.text.SimpleDateFormat)

Example 22 with PaymentGroup

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

the class CuFormatServiceImpl method startFormatProcess.

@Override
public FormatProcessSummary startFormatProcess(Person user, String campus, List<CustomerProfile> customers, Date paydate, String paymentTypes, String paymentDistribution) {
    LOG.debug("startFormatProcess() started");
    for (CustomerProfile element : customers) {
        LOG.debug("startFormatProcess() Customer: " + element);
    }
    // Create the process
    Date d = new Date();
    PaymentProcess paymentProcess = new PaymentProcess();
    paymentProcess.setCampusCode(campus);
    paymentProcess.setProcessUser(user);
    paymentProcess.setProcessTimestamp(new Timestamp(d.getTime()));
    this.businessObjectService.save(paymentProcess);
    // add an entry in the format process table (to lock the format process)
    FormatProcess formatProcess = new FormatProcess();
    formatProcess.setPhysicalCampusProcessCode(campus);
    formatProcess.setBeginFormat(dateTimeService.getCurrentTimestamp());
    formatProcess.setPaymentProcIdentifier(paymentProcess.getId().intValue());
    this.businessObjectService.save(formatProcess);
    Timestamp now = new Timestamp((new Date()).getTime());
    java.sql.Date sqlDate = new java.sql.Date(paydate.getTime());
    Calendar c = Calendar.getInstance();
    c.setTime(sqlDate);
    c.set(Calendar.HOUR, 11);
    c.set(Calendar.MINUTE, 59);
    c.set(Calendar.SECOND, 59);
    c.set(Calendar.MILLISECOND, 59);
    c.set(Calendar.AM_PM, Calendar.PM);
    Timestamp paydateTs = new Timestamp(c.getTime().getTime());
    LOG.debug("startFormatProcess() last update = " + now);
    LOG.debug("startFormatProcess() entered paydate = " + paydate);
    LOG.debug("startFormatProcess() actual paydate = " + paydateTs);
    PaymentStatus format = this.businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.FORMAT);
    List customerIds = new ArrayList();
    for (Iterator iter = customers.iterator(); iter.hasNext(); ) {
        CustomerProfile element = (CustomerProfile) iter.next();
        customerIds.add(element.getId());
    }
    // Mark all of them ready for format
    Iterator groupIterator = ((CuFormatPaymentDao) formatPaymentDao).markPaymentsForFormat(customerIds, paydateTs, paymentTypes, paymentDistribution);
    while (groupIterator.hasNext()) {
        PaymentGroup paymentGroup = (PaymentGroup) groupIterator.next();
        paymentGroup.setLastUpdatedTimestamp(paydateTs);
        paymentGroup.setPaymentStatus(format);
        paymentGroup.setProcess(paymentProcess);
        businessObjectService.save(paymentGroup);
    }
    // summarize them
    FormatProcessSummary preFormatProcessSummary = new FormatProcessSummary();
    Iterator<PaymentGroup> iterator = this.paymentGroupService.getByProcess(paymentProcess);
    while (iterator.hasNext()) {
        PaymentGroup paymentGroup = iterator.next();
        preFormatProcessSummary.add(paymentGroup);
    }
    // if no payments found for format clear the format process
    if (preFormatProcessSummary.getProcessSummaryList().size() == 0) {
        LOG.debug("startFormatProcess() No payments to process.  Format process ending");
        // ?? maybe call end format process
        clearUnfinishedFormat(paymentProcess.getId().intValue());
    }
    return preFormatProcessSummary;
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) Calendar(java.util.Calendar) ArrayList(java.util.ArrayList) CustomerProfile(org.kuali.kfs.pdp.businessobject.CustomerProfile) FormatProcess(org.kuali.kfs.pdp.businessobject.FormatProcess) Timestamp(java.sql.Timestamp) Date(java.util.Date) PaymentProcess(org.kuali.kfs.pdp.businessobject.PaymentProcess) FormatProcessSummary(org.kuali.kfs.pdp.businessobject.FormatProcessSummary) Iterator(java.util.Iterator) ArrayList(java.util.ArrayList) List(java.util.List) CuFormatPaymentDao(edu.cornell.kfs.pdp.dataaccess.CuFormatPaymentDao) PaymentStatus(org.kuali.kfs.pdp.businessobject.PaymentStatus)

Example 23 with PaymentGroup

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

the class CuPaymentFileServiceImpl method loadPayments.

@Override
public void loadPayments(PaymentFileLoad paymentFile, LoadPaymentStatus status, String incomingFileName) {
    status.setChart(paymentFile.getChart());
    status.setUnit(paymentFile.getUnit());
    status.setSubUnit(paymentFile.getSubUnit());
    status.setCreationDate(paymentFile.getCreationDate());
    status.setDetailCount(paymentFile.getActualPaymentCount());
    status.setDetailTotal(paymentFile.getCalculatedPaymentTotalAmount());
    // create batch record for payment load
    Batch batch = createNewBatch(paymentFile, getBaseFileName(incomingFileName));
    businessObjectService.save(batch);
    paymentFile.setBatchId(batch.getId());
    status.setBatchId(batch.getId());
    // do warnings and set defaults
    List<String> warnings = paymentFileValidationService.doSoftEdits(paymentFile);
    status.setWarnings(warnings);
    // store groups
    for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
        assignDisbursementTypeCode(paymentGroup);
        updatePaymentFieldsForEmployeePayee(paymentFile, paymentGroup);
        businessObjectService.save(paymentGroup);
    }
    // CU Customization: Check for and warn about inactive vendors.
    checkForInactiveVendors(paymentFile.getPaymentGroups(), batch.getCustomerProfile());
    // send list of warnings
    paymentFileEmailService.sendLoadEmail(paymentFile, warnings);
    if (paymentFile.isTaxEmailRequired()) {
        paymentFileEmailService.sendTaxEmail(paymentFile);
    }
    removeDoneFile(incomingFileName);
    LOG.debug("loadPayments() was successful");
    status.setLoadStatus(LoadPaymentStatus.LoadStatus.SUCCESS);
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) Batch(org.kuali.kfs.pdp.businessobject.Batch)

Example 24 with PaymentGroup

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

the class CuPaymentMaintenanceServiceImpl method cancelReissueDisbursement.

@Override
public boolean cancelReissueDisbursement(Integer paymentGroupId, String note, Person user) {
    LOG.debug("cancelReissueDisbursement() started");
    if (!pdpAuthorizationService.hasCancelPaymentPermission(user.getPrincipalId())) {
        LOG.warn("cancelReissueDisbursement() User " + user.getPrincipalId() + " does not have rights to cancel payments. This should not happen unless user is URL spoofing.");
        throw new RuntimeException("cancelReissueDisbursement() User " + user.getPrincipalId() + " does not have rights to cancel payments. This should not happen unless user is URL spoofing.");
    }
    PaymentGroup paymentGroup = this.paymentGroupService.get(paymentGroupId);
    if (paymentGroup == null) {
        LOG.debug("cancelReissueDisbursement() Disbursement not found; throw exception.");
        GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.PaymentDetail.ErrorMessages.ERROR_DISBURSEMENT_NOT_FOUND);
        return false;
    }
    String paymentStatus = paymentGroup.getPaymentStatus().getCode();
    if (!(PdpConstants.PaymentStatusCodes.OPEN.equals(paymentStatus))) {
        if (((PdpConstants.PaymentStatusCodes.EXTRACTED.equals(paymentStatus)) && (ObjectUtils.isNotNull(paymentGroup.getDisbursementDate()))) || (PdpConstants.PaymentStatusCodes.PENDING_ACH.equals(paymentStatus))) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("cancelReissueDisbursement() Payment status is " + paymentStatus + "; continue with cancel.");
            }
            List<PaymentGroup> allDisbursementPaymentGroups = this.paymentGroupService.getByDisbursementNumber(paymentGroup.getDisbursementNbr().intValue());
            for (PaymentGroup pg : allDisbursementPaymentGroups) {
                PaymentGroupHistory pgh = new PaymentGroupHistory();
                if (!pg.getPaymentDetails().get(0).isDisbursementActionAllowed()) {
                    LOG.warn("cancelDisbursement() Payment does not allow disbursement action. This should not happen unless user is URL spoofing.");
                    throw new RuntimeException("cancelDisbursement() Payment does not allow disbursement action. This should not happen unless user is URL spoofing.");
                }
                if ((ObjectUtils.isNotNull(pg.getDisbursementType())) && (pg.getDisbursementType().getCode().equals(PdpConstants.DisbursementTypeCodes.CHECK))) {
                    pgh.setPmtCancelExtractStat(Boolean.FALSE);
                }
                pgh.setOrigProcessImmediate(pg.getProcessImmediate());
                pgh.setOrigPmtSpecHandling(pg.getPymtSpecialHandling());
                pgh.setBank(pg.getBank());
                pgh.setOrigPaymentDate(pg.getPaymentDate());
                // put a check for null since disbursement date was not set in testMode / dev
                if (ObjectUtils.isNotNull(pg.getDisbursementDate())) {
                    pgh.setOrigDisburseDate(new Timestamp(pg.getDisbursementDate().getTime()));
                }
                pgh.setOrigAchBankRouteNbr(pg.getAchBankRoutingNbr());
                pgh.setOrigDisburseNbr(pg.getDisbursementNbr());
                pgh.setOrigAdviceEmail(pg.getAdviceEmailAddress());
                pgh.setDisbursementType(pg.getDisbursementType());
                pgh.setProcess(pg.getProcess());
                glPendingTransactionService.generateReissueGeneralLedgerPendingEntry(pg);
                LOG.debug("cancelReissueDisbursement() Status is '" + paymentStatus + "; delete row from AchAccountNumber table.");
                AchAccountNumber achAccountNumber = pg.getAchAccountNumber();
                if (ObjectUtils.isNotNull(achAccountNumber)) {
                    this.businessObjectService.delete(achAccountNumber);
                    pg.setAchAccountNumber(null);
                }
                // if bank functionality is not enabled or the group bank is inactive clear bank code
                if (!bankService.isBankSpecificationEnabled() || !pg.getBank().isActive()) {
                    pg.setBank(null);
                }
                pg.setDisbursementDate((java.sql.Date) null);
                pg.setAchBankRoutingNbr(null);
                pg.setAchAccountType(null);
                pg.setPhysCampusProcessCd(null);
                pg.setDisbursementNbr((KualiInteger) null);
                pg.setAdviceEmailAddress(null);
                // KFSPTS-1413 - do not reset the disb type as it prevents these payments from being picked up properly on reissue.
                // pg.setDisbursementType(null);
                pg.setProcess(null);
                pg.setProcessImmediate(false);
                changeStatus(pg, PdpConstants.PaymentStatusCodes.OPEN, PdpConstants.PaymentChangeCodes.CANCEL_REISSUE_DISBURSEMENT, note, user, pgh);
            }
            LOG.debug("cancelReissueDisbursement() Disbursement cancelled and reissued; exit method.");
        } else {
            LOG.debug("cancelReissueDisbursement() Payment status is " + paymentStatus + " and disbursement date is " + paymentGroup.getDisbursementDate() + "; cannot cancel payment");
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.PaymentDetail.ErrorMessages.ERROR_DISBURSEMENT_INVALID_TO_CANCEL_AND_REISSUE);
            return false;
        }
    } else {
        LOG.debug("cancelReissueDisbursement() Disbursement already cancelled and reissued; exit method.");
    }
    return true;
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) PaymentGroupHistory(org.kuali.kfs.pdp.businessobject.PaymentGroupHistory) AchAccountNumber(org.kuali.kfs.pdp.businessobject.AchAccountNumber) Timestamp(java.sql.Timestamp)

Example 25 with PaymentGroup

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

the class CuDisbursementVoucherExtractionHelperServiceImplTest method test.

public void test() {
    CuDisbursementVoucherDocument dv = null;
    try {
        dv = (CuDisbursementVoucherDocument) SpringContext.getBean(DocumentService.class).getNewDocument(DisbursementVoucherDocument.class);
    } catch (WorkflowException e) {
        throw new RuntimeException("Error creating new disbursement voucher document: " + e.getMessage(), e);
    }
    if (dv != null) {
        dv.getDocumentHeader().setDocumentDescription("Test Document Description");
        dv.getDocumentHeader().setExplanation("Stuff");
        dv.initiateDocument();
        VendorDetail vendor = SpringContext.getBean(VendorService.class).getVendorDetail("13366-0");
        VendorAddress vendoraddress = SpringContext.getBean(VendorService.class).getVendorDefaultAddress(vendor.getVendorHeaderGeneratedIdentifier(), vendor.getVendorDetailAssignedIdentifier(), "RM", "");
        System.out.println(vendoraddress.getVendorCityName() + "\n");
        dv.templateVendor(vendor, vendoraddress);
        dv.setPayeeAssigned(true);
        dv.getDvPayeeDetail().setDisbVchrPaymentReasonCode("S");
        dv.setDisbVchrCheckTotalAmount(new KualiDecimal(86.00));
        dv.setDisbVchrPaymentMethodCode("P");
        dv.setDisbVchrCheckStubText("check text");
        dv.setCampusCode("IT");
        SourceAccountingLine accountingLine = new SourceAccountingLine();
        accountingLine.setChartOfAccountsCode("IT");
        accountingLine.setAccountNumber("G081040");
        accountingLine.setFinancialObjectCode("2045");
        accountingLine.setAmount((new KualiDecimal(86.00)));
        accountingLine.setPostingYear(dv.getPostingYear());
        accountingLine.setDocumentNumber(dv.getDocumentNumber());
        dv.addSourceAccountingLine(accountingLine);
        try {
            documentService.saveDocument(dv);
        } catch (WorkflowException e) {
            throw new RuntimeException("Error saving new disbursement voucher document: " + e.getMessage(), e);
        }
    }
    Date transactionTimestamp = new Date(SpringContext.getBean(DateTimeService.class).getCurrentDate().getTime());
    Date processRunDate = new java.sql.Date(transactionTimestamp.getTime());
    PaymentGroup pg = cuDisbursementVoucherExtractionHelperService.createPaymentGroup(dv, processRunDate);
    assertTrue(pg.getPaymentDetails().get(0).getCustPaymentDocNbr().equalsIgnoreCase(dv.getDocumentNumber()));
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) WorkflowException(org.kuali.rice.kew.api.exception.WorkflowException) SourceAccountingLine(org.kuali.kfs.sys.businessobject.SourceAccountingLine) DocumentService(org.kuali.kfs.krad.service.DocumentService) Date(java.sql.Date) VendorDetail(org.kuali.kfs.vnd.businessobject.VendorDetail) VendorService(org.kuali.kfs.vnd.document.service.VendorService) CuDisbursementVoucherDocument(edu.cornell.kfs.fp.document.CuDisbursementVoucherDocument) KualiDecimal(org.kuali.rice.core.api.util.type.KualiDecimal) VendorAddress(org.kuali.kfs.vnd.businessobject.VendorAddress) DateTimeService(org.kuali.rice.core.api.datetime.DateTimeService)

Aggregations

PaymentGroup (org.kuali.kfs.pdp.businessobject.PaymentGroup)25 PaymentDetail (org.kuali.kfs.pdp.businessobject.PaymentDetail)11 KualiDecimal (org.kuali.rice.core.api.util.type.KualiDecimal)9 HashMap (java.util.HashMap)7 KualiInteger (org.kuali.rice.core.api.util.type.KualiInteger)7 Date (java.util.Date)6 BufferedWriter (java.io.BufferedWriter)5 IOException (java.io.IOException)5 CustomerProfile (org.kuali.kfs.pdp.businessobject.CustomerProfile)5 FileWriter (java.io.FileWriter)4 Timestamp (java.sql.Timestamp)4 Iterator (java.util.Iterator)4 List (java.util.List)4 SimpleDateFormat (java.text.SimpleDateFormat)3 ArrayList (java.util.ArrayList)3 HashSet (java.util.HashSet)3 Map (java.util.Map)3 PaymentStatus (org.kuali.kfs.pdp.businessobject.PaymentStatus)3 VendorDetail (org.kuali.kfs.vnd.businessobject.VendorDetail)3 CuDisbursementVoucherDocument (edu.cornell.kfs.fp.document.CuDisbursementVoucherDocument)2