Search in sources :

Example 11 with CustomerProfile

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

the class CuExtractPaymentServiceImpl method writeExtractAchFileMellonBankFastTrack.

// Adjusted the code in this method to deal with the output of the payment details based upon
// payee with multiple payments in the same payment group.
protected void writeExtractAchFileMellonBankFastTrack(PaymentStatus extractedStatus, String filename, Date processDate, SimpleDateFormat sdf, List<String> notificationEmailAddresses) {
    BufferedWriter os = null;
    // Used in the Fast Track file HEADER record
    sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    // headerDate must be day after processDate to prevent additional cost for same day ACH payments
    Date headerDate = calculateHeaderDate(processDate);
    // Used in the Fast Track file PAY01000 record
    SimpleDateFormat sdfPAY1000Rec = new SimpleDateFormat("yyyyMMdd");
    // 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 = "";
    boolean specialHandlingCode = false;
    boolean attachmentCode = false;
    String divisionCode = "";
    String achCode = "";
    String dateQualifer = "";
    boolean wroteFastTrackHeaderRecords = false;
    // Change the filename so that it ends in .txt instead of .xml.
    filename = filename.replace(".xml", ".txt");
    int totalRecordCount = 0;
    KualiDecimal totalPaymentAmounts = KualiDecimal.ZERO;
    CustomerProfile cp = null;
    String sCountryName = "";
    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";
        HashSet<String> bankCodes = this.getAchBundlerHelperService().getDistinctBankCodesForPendingAchPayments();
        for (String bankCode : bankCodes) {
            HashSet<Integer> disbNbrs = this.getAchBundlerHelperService().getDistinctDisbursementNumbersForPendingAchPaymentsByBankCode(bankCode);
            for (Iterator<Integer> iter = disbNbrs.iterator(); iter.hasNext(); ) {
                Integer disbursementNbr = iter.next();
                boolean first = true;
                // compute total net amount as it is needed on first payment detail
                KualiDecimal totalNetAmount = new KualiDecimal(0);
                Iterator<PaymentDetail> payDetailIter = this.getAchBundlerHelperService().getPendingAchPaymentDetailsByDisbursementNumberAndBank(disbursementNbr, bankCode);
                while (payDetailIter.hasNext()) {
                    PaymentDetail pd = payDetailIter.next();
                    totalNetAmount = totalNetAmount.add(pd.getNetPaymentAmount());
                }
                Iterator<PaymentDetail> paymentDetails = this.getAchBundlerHelperService().getPendingAchPaymentDetailsByDisbursementNumberAndBank(disbursementNbr, bankCode);
                while (paymentDetails.hasNext()) {
                    PaymentDetail pd = paymentDetails.next();
                    PaymentGroup pg = pd.getPaymentGroup();
                    // Get our Bank Account Number and our bank routing number
                    ourBankAccountNumber = pg.getBank().getBankAccountNumber().replace("-", "");
                    ourBankRoutingNumber = pg.getBank().getBankRoutingNumber();
                    if (!wroteFastTrackHeaderRecords) {
                        // open the file for writing
                        os = new BufferedWriter(new FileWriter(filename));
                        // Write the Fast Track header record (FIL00010) once for each file
                        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.  This has to be different for ACH than it is for CHECKS per BNY Mellon.
                        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(headerDate) + // File Date and Time - 14 Bytes  YYMMDD format
                        cDelim + // Reserved Field - 3 Bytes
                        cDelim + "\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();
                            os.write("FIL00020" + cDelim + emailAddress + cDelim + cDelim + "\n");
                            totalRecordCount = totalRecordCount + 1;
                        }
                        wroteFastTrackHeaderRecords = true;
                    }
                    if (first) {
                        // Get country name for code
                        int CountryNameMaxLength = 15;
                        Country country = null;
                        // KFSUPGRADE-859 check if the country name is not blank, otherwise country service will throw exception
                        if (StringUtils.isNotBlank(pg.getCountry())) {
                            country = this.getCountryService().getCountry(pg.getCountry());
                        }
                        if (country != null) {
                            sCountryName = country.getName().substring(0, ((country.getName().length() >= CountryNameMaxLength) ? CountryNameMaxLength : country.getName().length()));
                            if (sCountryName.toUpperCase().contains("UNITED STATES"))
                                sCountryName = "";
                        } else if (ObjectUtils.isNotNull(pg.getCountry()))
                            sCountryName = pg.getCountry().substring(0, ((pg.getCountry().length() >= CountryNameMaxLength) ? CountryNameMaxLength : pg.getCountry().length()));
                        if (sCountryName.toUpperCase().contains("UNITED STATES"))
                            sCountryName = "";
                        else
                            sCountryName = "";
                        int dvCodeInt = 0;
                        // 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());
                            else
                                LOG.error("No customer profile exists for payee name: " + pg.getPayeeName());
                        }
                        // Get special handling indicator
                        specialHandlingCode = pg.getPymtSpecialHandling();
                        // Get attachment indicator
                        attachmentCode = pg.getPymtAttachment();
                        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);
                        // Determine the ACH Code to send down.  Here are the rules:
                        // 1.  If they are a vendor and they've selected checking account, the ACH code is CTX (vendors can only have ACH to checking)
                        // 2.  If they are a vendor and they've selected savings account, the ACH code is PPD (sometimes sole proprietors are vendors and they use personal not corporate accounts)
                        // 3.  If they are NOT a vendor, then the ACH code is always PPD (allows employees to have their's deposited in checking or savings)
                        // Determine which ACH code to use: CTX for corporate accounts, or PPD for Personal accounts
                        // String payeeId = paymentGroup.getPayeeId();                          // Returns the ID of the payee and is the vendor number if the next var indicates that
                        // String payeeIdTypeDesc = paymentGroup.getPayeeIdTypeDesc();          // Returns "Vendor Number" if the payeeID is a vendor number
                        String AchBankRoutingNbr = "";
                        String AchAccountType = "DA";
                        String AchBankAccountNumber = "";
                        String CheckNumber = "";
                        // payeeIdTypeCode returns a "V" if it is a vendor
                        boolean isVendor = (pg.getPayeeIdTypeCd().equals("V")) ? true : false;
                        String kfsAccountType = "";
                        if (ObjectUtils.isNotNull(pg.getAchAccountType()))
                            // Returns either a 22 for checking or a 32 for Savings account
                            kfsAccountType = pg.getAchAccountType();
                        // For Mellon this converts to either DA (checking) or SG (savings)
                        if (kfsAccountType.startsWith("22")) {
                            AchAccountType = "DA";
                        }
                        if (kfsAccountType.startsWith("32")) {
                            AchAccountType = "SG";
                        }
                        if (kfsAccountType.contains("PPD")) {
                            achCode = "PPD";
                        }
                        if (kfsAccountType.contains("CTX")) {
                            achCode = "CTX";
                        }
                        if (ObjectUtils.isNotNull(pg.getAchBankRoutingNbr()))
                            AchBankRoutingNbr = pg.getAchBankRoutingNbr();
                        if (ObjectUtils.isNotNull(pg.getAchAccountNumber().getAchBankAccountNbr()))
                            AchBankAccountNumber = pg.getAchAccountNumber().getAchBankAccountNbr().toString();
                        if (ObjectUtils.isNotNull(pg.getDisbursementNbr().toString()))
                            CheckNumber = pg.getDisbursementNbr().toString();
                        // Write only 1 PAY01000 record for each payee
                        os.write(// Record Type - 8 bytes
                        "PAY01000" + cDelim + "1" + // 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 + "ACH" + // ACH= ACH Payment method - 3 Bytes
                        cDelim + achCode + // PPD is used for ACH payments to a personal bank account - 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 + "1150532082" + // Originating company identifier - 10 bytes - This has to be different for ACH than it is for CHECKS per BNY Mellon
                        cDelim + "01" + // Receiving bank id qualifier - 2 bytes
                        cDelim + AchBankRoutingNbr + // Receiving bank id - 12 bytes
                        cDelim + AchAccountType + // Receiving account number qualifier - 3 bytes - must be DA for checking or SG for savings.
                        cDelim + AchBankAccountNumber + // Receiving account number - 35 bytes
                        cDelim + sdfPAY1000Rec.format(processDate) + // Effective date - 8 bytes - YYMMDDHHMMSS
                        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 + cDelim + "\n");
                        totalPaymentAmounts = totalPaymentAmounts.add(totalNetAmount);
                        // Write the Payer Detail(PDT02010) record for the payer (us) (only one per payee)
                        os.write(// Record Type - 8 bytes
                        "PDT02010" + cDelim + "PR" + // Name qualifier - 3 bytes
                        cDelim + // ID code qualifier - 2 bytes
                        cDelim + // ID code - 80 bytes
                        cDelim + PAYER_NAME + // Name - 60 bytes
                        cDelim + // Additional name 1 - 60 bytes
                        cDelim + // Additional name 2 - 60 bytes
                        cDelim + PAYER_ADDRESS_LINE1 + // Address line 1 - 55 bytes
                        cDelim + PAYER_ADDRESS_LINE2 + // Address line 2 - 55 bytes
                        cDelim + // Address line 3 - 55 bytes
                        cDelim + // Address line 4 - 55 bytes
                        cDelim + // Address line 5 - 55 bytes
                        cDelim + // Address line 6 - 55 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 payee detail (PDT02010) record for the payee (them) (only one per payee)
                        // temp variables to observe length limitations
                        int AddrMaxLength = 35;
                        int CityMaxLength = 30;
                        int StateMaxLength = 2;
                        int ZipMaxLength = 15;
                        int PayeeNameMaxLength = 35;
                        String PayeeName = "";
                        String AddrLine1 = "";
                        String AddrLine2 = "";
                        String AddrLine3 = "";
                        String AddrLine4 = "";
                        String City = "";
                        String State = "";
                        String Zip = "";
                        if (ObjectUtils.isNotNull(pg.getPayeeName()))
                            PayeeName = pg.getPayeeName().substring(0, ((pg.getPayeeName().length() >= PayeeNameMaxLength) ? PayeeNameMaxLength : pg.getPayeeName().length()));
                        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()))
                            City = pg.getCity().substring(0, ((pg.getCity().length() >= CityMaxLength) ? CityMaxLength : pg.getCity().length()));
                        if (ObjectUtils.isNotNull(pg.getState()))
                            State = pg.getState().substring(0, ((pg.getState().length() >= StateMaxLength) ? StateMaxLength : pg.getState().length()));
                        if (ObjectUtils.isNotNull(pg.getZipCd()))
                            Zip = (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
                        cDelim + // ID code qualifier - 2 bytes
                        cDelim + // ID code - 80 bytes
                        cDelim + PayeeName + // Name - 30 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 + City + // City - 30 bytes
                        cDelim + State + // State/Province - 2 bytes
                        cDelim + Zip + // Postal code - 15 bytes
                        cDelim + // Country code - 3 bytes
                        cDelim + sCountryName + // Country name - 30 bytes  (do not use is Country Code is used)
                        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 one for each PDT02010 record
                        totalRecordCount = totalRecordCount + 3;
                        // Set this here so it is only executed once per payee
                        first = false;
                    }
                    // If (first)
                    // Write the REM03020 record
                    // Set up remittanceIdCode and remittanceIdText based on whether its a DV or something else.
                    String remittanceIdCode = (subUnitCode.equals(CuDisbursementVoucherConstants.DV_EXTRACT_SUB_UNIT_CODE)) ? "TN" : "IV";
                    String remittanceIdText = (subUnitCode.equals(CuDisbursementVoucherConstants.DV_EXTRACT_SUB_UNIT_CODE)) ? ObjectUtils.isNotNull(pd.getCustPaymentDocNbr()) ? "Doc No:" + pd.getCustPaymentDocNbr() : "" : ObjectUtils.isNotNull(pd.getInvoiceNbr()) ? pd.getInvoiceNbr() : "";
                    // 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");
                            break;
                        }
                    }
                    if (ObjectUtils.isNotNull(pd.getOrigInvoiceAmount())) {
                        ftTotalAmount = pd.getOrigInvoiceAmount().toString();
                        if (ftTotalAmount.length() > 18) {
                            LOG.error("Original Invoice Amount is more than 18 bytes");
                            break;
                        }
                    }
                    if (ObjectUtils.isNotNull(pd.getInvTotDiscountAmount())) {
                        ftDiscountAmt = pd.getInvTotDiscountAmount().toString();
                        if (ftDiscountAmt.length() > 18) {
                            LOG.error("Discount Amount is more than 18 bytes");
                            break;
                        }
                    }
                    String InvoiceDate = "";
                    if (ObjectUtils.isNotNull(pd.getInvoiceDate())) {
                        InvoiceDate = pd.getInvoiceDate().toString().replace("-", "");
                        dateQualifer = "003";
                    }
                    os.write(// Record type - 8 bytes
                    "REM03020" + cDelim + remittanceIdCode + // Remittance qualifier code - 3 bytes
                    cDelim + remittanceIdText + // Remittance ID - 50 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 - NOT USED PER SPEC
                    cDelim + // Note 2 - 80 bytes - NOT USED PER SPEC
                    cDelim + // Ref qualifier 1
                    cDelim + // Ref ID 1
                    cDelim + // Ref description 1
                    cDelim + // Ref qualifier 2
                    cDelim + // Ref ID 2
                    cDelim + // Ref description 2
                    cDelim + // Ref qualifier 3
                    cDelim + // Ref ID 3
                    cDelim + // Ref description 3
                    cDelim + // Ref qualifier 4
                    cDelim + // Ref ID 4
                    cDelim + // Ref description 4
                    cDelim + dateQualifer + // Date qualifier 1
                    cDelim + InvoiceDate + // Date 1
                    cDelim + // Date qualifier 2
                    cDelim + // Date 2
                    cDelim + // Date qualifier 3
                    cDelim + // Date 3
                    cDelim + // Date qualifier 4
                    cDelim + cDelim + "\n");
                    // One for the REM03020 record
                    totalRecordCount = totalRecordCount + 1;
                }
            // while there are payment details
            }
        // for each disbNbr
        }
        if (wroteFastTrackHeaderRecords) {
            // Need to update the total record count here to make sure it includes the trailer record
            totalRecordCount = totalRecordCount + 1;
            // Now write the 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("extractAchPayments() Problem reading file:  " + filename, ie);
        throw new IllegalArgumentException("Error writing to output file: " + ie.getMessage());
    } catch (Exception ex) {
        LOG.error("General Exception with writeExtractBundledAchFileMellonBankFastTrack().  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(filename, filename + ".READY");
            } catch (IOException ie) {
                // Not much we can do now
                LOG.error("IOException in extractAchPayments():  " + filename, ie);
            }
        }
    }
}
Also used : PaymentGroup(org.kuali.kfs.pdp.businessobject.PaymentGroup) FileWriter(java.io.FileWriter) CustomerProfile(org.kuali.kfs.pdp.businessobject.CustomerProfile) IOException(java.io.IOException) Date(java.util.Date) 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) Country(org.kuali.rice.location.api.country.Country) SimpleDateFormat(java.text.SimpleDateFormat)

Example 12 with CustomerProfile

use of org.kuali.kfs.pdp.businessobject.CustomerProfile 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 13 with CustomerProfile

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

the class CuExtractPaymentServiceImpl method writePayeeSpecificsToAchFile.

/*
     * New method created due to refactoring the code from ExtractPaymentServiceImpl and AchBundlerExtractPaymnetServiceImpl.
     * Method writes all tags and data for a single payee from open ach to close of customerProfile.
     */
protected void writePayeeSpecificsToAchFile(BufferedWriter os, PaymentGroup paymentGroup, Date processDate, SimpleDateFormat sdf) throws IOException {
    try {
        writeOpenTagAttribute(os, 2, "ach", "disbursementNbr", paymentGroup.getDisbursementNbr().toString());
        PaymentProcess paymentProcess = paymentGroup.getProcess();
        writeTag(os, 4, "processCampus", paymentProcess.getCampusCode());
        writeTag(os, 4, "processId", paymentProcess.getId().toString());
        writeBank(os, 4, paymentGroup.getBank());
        writeTag(os, 4, "disbursementDate", sdf.format(processDate));
        writeTag(os, 4, "netAmount", paymentGroup.getNetPaymentAmount().toString());
        writePayeeAch(os, 4, paymentGroup);
        writeTag(os, 4, "paymentDate", sdf.format(paymentGroup.getPaymentDate()));
        CustomerProfile cp = paymentGroup.getBatch().getCustomerProfile();
        writeCustomerProfile(os, 4, cp);
        writeOpenTag(os, 4, "payments");
    } catch (IOException ioe) {
        LOG.error("writePayeeSpecificsToAchFile(): Problem writing to file - IOException caught and rethrown.");
        throw ioe;
    }
}
Also used : PaymentProcess(org.kuali.kfs.pdp.businessobject.PaymentProcess) CustomerProfile(org.kuali.kfs.pdp.businessobject.CustomerProfile) IOException(java.io.IOException)

Example 14 with CustomerProfile

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

the class CuPendingTransactionServiceImpl method populatePaymentGeneralLedgerPendingEntry.

/**
 * Populates and stores a new GLPE for each account detail in the payment group.
 *
 * @param paymentGroup payment group to generate entries for
 * @param achFdocTypeCode doc type for ach disbursements
 * @param checkFdocTypeCod doc type for check disbursements
 * @param reversal boolean indicating if this is a reversal
 */
protected void populatePaymentGeneralLedgerPendingEntry(PaymentGroup paymentGroup, String achFdocTypeCode, String checkFdocTypeCod, boolean reversal) {
    List<PaymentAccountDetail> accountListings = new ArrayList<PaymentAccountDetail>();
    GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper();
    for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
        accountListings.addAll(paymentDetail.getAccountDetail());
    }
    // GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper();
    for (PaymentAccountDetail paymentAccountDetail : accountListings) {
        GlPendingTransaction glPendingTransaction = new GlPendingTransaction();
        glPendingTransaction.setSequenceNbr(new KualiInteger(sequenceHelper.getSequenceCounter()));
        if (StringUtils.isNotBlank(paymentAccountDetail.getPaymentDetail().getFinancialSystemOriginCode()) && StringUtils.isNotBlank(paymentAccountDetail.getPaymentDetail().getFinancialDocumentTypeCode())) {
            glPendingTransaction.setFdocRefTypCd(paymentAccountDetail.getPaymentDetail().getFinancialDocumentTypeCode());
            glPendingTransaction.setFsRefOriginCd(paymentAccountDetail.getPaymentDetail().getFinancialSystemOriginCode());
        } else {
            glPendingTransaction.setFdocRefTypCd(PdpConstants.PDP_FDOC_TYPE_CODE);
            glPendingTransaction.setFsRefOriginCd(PdpConstants.PDP_FDOC_ORIGIN_CODE);
        }
        glPendingTransaction.setFinancialBalanceTypeCode(org.kuali.kfs.sys.KFSConstants.BALANCE_TYPE_ACTUAL);
        Date transactionTimestamp = new Date(dateTimeService.getCurrentDate().getTime());
        glPendingTransaction.setTransactionDt(transactionTimestamp);
        AccountingPeriod fiscalPeriod = accountingPeriodService.getByDate(new java.sql.Date(transactionTimestamp.getTime()));
        glPendingTransaction.setUniversityFiscalYear(fiscalPeriod.getUniversityFiscalYear());
        glPendingTransaction.setUnivFiscalPrdCd(fiscalPeriod.getUniversityFiscalPeriodCode());
        glPendingTransaction.setAccountNumber(paymentAccountDetail.getAccountNbr());
        glPendingTransaction.setSubAccountNumber(paymentAccountDetail.getSubAccountNbr());
        glPendingTransaction.setChartOfAccountsCode(paymentAccountDetail.getFinChartCode());
        if (paymentGroup.getDisbursementType().getCode().equals(PdpConstants.DisbursementTypeCodes.ACH)) {
            glPendingTransaction.setFinancialDocumentTypeCode(achFdocTypeCode);
        } else if (paymentGroup.getDisbursementType().getCode().equals(PdpConstants.DisbursementTypeCodes.CHECK)) {
            glPendingTransaction.setFinancialDocumentTypeCode(checkFdocTypeCod);
        }
        glPendingTransaction.setFsOriginCd(PdpConstants.PDP_FDOC_ORIGIN_CODE);
        glPendingTransaction.setFdocNbr(paymentGroup.getDisbursementNbr().toString());
        // if stale
        if (StringUtils.equals(FDOC_TYP_CD_STALE_CHECK, checkFdocTypeCod)) {
            ParameterService parameterService = SpringContext.getBean(ParameterService.class);
            String clAcct = parameterService.getParameterValueAsString(CheckReconciliationImportStep.class, CRConstants.CLEARING_ACCOUNT);
            String obCode = parameterService.getParameterValueAsString(CheckReconciliationImportStep.class, CRConstants.CLEARING_OBJECT_CODE);
            String coaCode = parameterService.getParameterValueAsString(CheckReconciliationImportStep.class, CRConstants.CLEARING_COA);
            // Use clearing parameters if stale
            glPendingTransaction.setAccountNumber(clAcct);
            glPendingTransaction.setFinancialObjectCode(obCode);
            glPendingTransaction.setChartOfAccountsCode(coaCode);
            glPendingTransaction.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
            // KFSUPGRADE-943
            glPendingTransaction.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
        } else {
            Boolean relieveLiabilities = paymentGroup.getBatch().getCustomerProfile().getRelieveLiabilities();
            if ((relieveLiabilities != null) && (relieveLiabilities.booleanValue()) && paymentAccountDetail.getPaymentDetail().getFinancialDocumentTypeCode() != null) {
                OffsetDefinition offsetDefinition = SpringContext.getBean(OffsetDefinitionService.class).getByPrimaryId(glPendingTransaction.getUniversityFiscalYear(), glPendingTransaction.getChartOfAccountsCode(), paymentAccountDetail.getPaymentDetail().getFinancialDocumentTypeCode(), glPendingTransaction.getFinancialBalanceTypeCode());
                glPendingTransaction.setFinancialObjectCode(offsetDefinition != null ? offsetDefinition.getFinancialObjectCode() : paymentAccountDetail.getFinObjectCode());
                glPendingTransaction.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
            } else {
                glPendingTransaction.setFinancialObjectCode(paymentAccountDetail.getFinObjectCode());
                glPendingTransaction.setFinancialSubObjectCode(paymentAccountDetail.getFinSubObjectCode());
            }
        }
        glPendingTransaction.setProjectCd(paymentAccountDetail.getProjectCode());
        glPendingTransaction.setDebitCrdtCd(pdpUtilService.isDebit(paymentAccountDetail, reversal) ? KFSConstants.GL_DEBIT_CODE : KFSConstants.GL_CREDIT_CODE);
        glPendingTransaction.setAmount(paymentAccountDetail.getAccountNetAmount().abs());
        // Changes for Research Participant Upload
        String trnDesc = StringUtils.EMPTY;
        CustomerProfile customerProfile = paymentGroup.getBatch().getCustomerProfile();
        // KFSUPGRADE-973
        if (researchParticipantPaymentValidationService.isResearchParticipantPayment(customerProfile)) {
            BusinessObjectEntry businessObjectEntry = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(PaymentDetail.class.getName());
            AttributeDefinition attributeDefinition = businessObjectEntry.getAttributeDefinition("paymentGroup.payeeName");
            AttributeSecurity originalPayeeNameAttributeSecurity = attributeDefinition.getAttributeSecurity();
            // This is a temporary work around for an issue introduced with KFSCNTRB-705.
            if (ObjectUtils.isNotNull(originalPayeeNameAttributeSecurity)) {
                String maskLiteral = ((MaskFormatterLiteral) originalPayeeNameAttributeSecurity.getMaskFormatter()).getLiteral();
                trnDesc = maskLiteral;
            }
        } else {
            String payeeName = paymentGroup.getPayeeName();
            if (StringUtils.isNotBlank(payeeName)) {
                trnDesc = payeeName.length() > 40 ? payeeName.substring(0, 40) : StringUtils.rightPad(payeeName, 40);
            }
            if (reversal) {
                String poNbr = paymentAccountDetail.getPaymentDetail().getPurchaseOrderNbr();
                if (StringUtils.isNotBlank(poNbr)) {
                    trnDesc += " " + (poNbr.length() > 9 ? poNbr.substring(0, 9) : StringUtils.rightPad(poNbr, 9));
                }
                String invoiceNbr = paymentAccountDetail.getPaymentDetail().getInvoiceNbr();
                if (StringUtils.isNotBlank(invoiceNbr)) {
                    trnDesc += " " + (invoiceNbr.length() > 14 ? invoiceNbr.substring(0, 14) : StringUtils.rightPad(invoiceNbr, 14));
                }
                if (trnDesc.length() > 40) {
                    trnDesc = trnDesc.substring(0, 40);
                }
            }
        }
        glPendingTransaction.setDescription(trnDesc);
        glPendingTransaction.setOrgDocNbr(paymentAccountDetail.getPaymentDetail().getOrganizationDocNbr());
        glPendingTransaction.setOrgReferenceId(paymentAccountDetail.getOrgReferenceId());
        glPendingTransaction.setFdocRefNbr(paymentAccountDetail.getPaymentDetail().getCustPaymentDocNbr());
        // update the offset account if necessary
        SpringContext.getBean(FlexibleOffsetAccountService.class).updateOffset(glPendingTransaction);
        this.businessObjectService.save(glPendingTransaction);
        sequenceHelper.increment();
        if (bankService.isBankSpecificationEnabled()) {
            this.populateBankOffsetEntry(paymentGroup, glPendingTransaction, sequenceHelper);
        }
    }
}
Also used : ParameterService(org.kuali.kfs.coreservice.framework.parameter.ParameterService) GlPendingTransaction(org.kuali.kfs.pdp.businessobject.GlPendingTransaction) KualiInteger(org.kuali.rice.core.api.util.type.KualiInteger) AccountingPeriod(org.kuali.kfs.coa.businessobject.AccountingPeriod) ArrayList(java.util.ArrayList) AttributeDefinition(org.kuali.kfs.krad.datadictionary.AttributeDefinition) CustomerProfile(org.kuali.kfs.pdp.businessobject.CustomerProfile) GeneralLedgerPendingEntrySequenceHelper(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper) Date(java.sql.Date) Date(java.sql.Date) PaymentDetail(org.kuali.kfs.pdp.businessobject.PaymentDetail) BusinessObjectEntry(org.kuali.kfs.krad.datadictionary.BusinessObjectEntry) FlexibleOffsetAccountService(org.kuali.kfs.sys.service.FlexibleOffsetAccountService) OffsetDefinition(org.kuali.kfs.coa.businessobject.OffsetDefinition) OffsetDefinitionService(org.kuali.kfs.coa.service.OffsetDefinitionService) AttributeSecurity(org.kuali.kfs.krad.datadictionary.AttributeSecurity) PaymentAccountDetail(org.kuali.kfs.pdp.businessobject.PaymentAccountDetail) MaskFormatterLiteral(org.kuali.kfs.krad.datadictionary.mask.MaskFormatterLiteral)

Example 15 with CustomerProfile

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

the class CuFormatAction method start.

@Override
public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
    CuFormatForm formatForm = (CuFormatForm) form;
    Person kualiUser = GlobalVariables.getUserSession().getPerson();
    FormatSelection formatSelection = formatService.getDataForFormat(kualiUser);
    DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class);
    formatForm.setCampus(kualiUser.getCampusCode());
    // no data for format because another format process is already running
    if (formatSelection.getStartDate() != null) {
        GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.Format.ERROR_PDP_FORMAT_PROCESS_ALREADY_RUNNING, dateTimeService.toDateTimeString(formatSelection.getStartDate()));
    } else {
        List<CustomerProfile> customers = formatSelection.getCustomerList();
        for (CustomerProfile element : customers) {
            if (formatSelection.getCampus().equals(element.getDefaultPhysicalCampusProcessingCode())) {
                element.setSelectedForFormat(Boolean.TRUE);
            } else {
                element.setSelectedForFormat(Boolean.FALSE);
            }
        }
        formatForm.setPaymentDate(dateTimeService.toDateString(dateTimeService.getCurrentTimestamp()));
        formatForm.setPaymentTypes(PdpConstants.PaymentTypes.ALL);
        formatForm.setPaymentDistribution(CUPdpConstants.PaymentDistributions.PROCESS_ALL);
        formatForm.setCustomers(customers);
        formatForm.setRanges(formatSelection.getRangeList());
    }
    return mapping.findForward(PdpConstants.MAPPING_SELECTION);
}
Also used : FormatSelection(org.kuali.kfs.pdp.businessobject.FormatSelection) CustomerProfile(org.kuali.kfs.pdp.businessobject.CustomerProfile) Person(org.kuali.rice.kim.api.identity.Person) DateTimeService(org.kuali.rice.core.api.datetime.DateTimeService)

Aggregations

CustomerProfile (org.kuali.kfs.pdp.businessobject.CustomerProfile)17 ArrayList (java.util.ArrayList)6 Date (java.util.Date)6 PaymentDetail (org.kuali.kfs.pdp.businessobject.PaymentDetail)6 KualiInteger (org.kuali.rice.core.api.util.type.KualiInteger)6 IOException (java.io.IOException)5 PaymentGroup (org.kuali.kfs.pdp.businessobject.PaymentGroup)5 DateTimeService (org.kuali.rice.core.api.datetime.DateTimeService)4 Person (org.kuali.rice.kim.api.identity.Person)4 BufferedWriter (java.io.BufferedWriter)3 FileWriter (java.io.FileWriter)3 Timestamp (java.sql.Timestamp)3 SimpleDateFormat (java.text.SimpleDateFormat)3 List (java.util.List)3 FormatProcessSummary (org.kuali.kfs.pdp.businessobject.FormatProcessSummary)3 KualiDecimal (org.kuali.rice.core.api.util.type.KualiDecimal)3 Date (java.sql.Date)2 Calendar (java.util.Calendar)2 Iterator (java.util.Iterator)2 AccountingPeriod (org.kuali.kfs.coa.businessobject.AccountingPeriod)2