NACHA Files Part 3

In Part 1 we created some classes. In Part 2 we created some more classes, linked them, and filled them. Now we’re ready to actually produce some results. In a standard module, I write my code to generate the XML file.

Public Sub GenerateACH()
   
    Set gshData = wshChecks
   
    FillClasses gshData
    gclsEmployees.GenerateACH #1/21/2011#
   
End Sub

Man, do I love simple code. In Part 2, I went on and on about my coding method. I start with a procedure like this and work backward to the details. At this point, I need to actually create the GenerateACH method and I know I’ll need to supply a check date. With a few exceptions, my code won’t compile until I’m done.

Public Sub GenerateACH(dtCheck As Date)
   
    Dim clsEmployee As CEmployee
    Dim sOutput As String
    Dim sFile As String, lFile As Long
    Dim lSeq As Long
    Dim dNetPay As Double
   
    sFile = ThisWorkbook.Path & Application.PathSeparator & Format(dtCheck, “yyyymmdd”) & “.wrk”
    dNetPay = Me.NetPay(dtCheck)
   
    sOutput = gsACHEDITOR & vbNewLine
   
    For Each clsEmployee In Me
        sOutput = sOutput & clsEmployee.GenerateACH(dtCheck, lSeq)
    Next clsEmployee
   
    lSeq = lSeq + 1
    sOutput = sOutput & Me.ACHTotalEditorTable(dtCheck, lSeq, dNetPay)
    sOutput = sOutput & Me.FileSpec
    sOutput = sOutput & Me.BatchTotal(lSeq, dNetPay)
   
    sOutput = sOutput & TagClose(gsACHEDITOR)
   
    lFile = FreeFile
    Open sFile For Output As lFile
    Print #lFile, sOutput
    Close lFile
   
End Sub

In Ruby on Rails, the mantra is “Keep your models heavy and your controllers light”. In my version of VBA, that translates into heavy classes and light standard modules. Procedures in standard modules should demonstrate the basic framework while the classes to the dirty detail work. In the above procedure, I build a string, sOutput, that I will eventually print to a file.

The XML file generally consists of one block of tags for each employee and a few other blocks for totals and other company information. This code loops through the employees and generates the tag blocks, all the while concatenating to sOutput.

My first compile error tells me I need a NetPay property.

Public Property Get NetPay(dtCheck As Date) As Double
   
    Dim clsEmployee As CEmployee
    Dim clsCheck As CCheck
    Dim dReturn As Double
   
    For Each clsEmployee In Me
        Set clsCheck = clsEmployee.CheckByDate(dtCheck)
        If Not clsCheck Is Nothing Then
            dReturn = dReturn + clsCheck.NetPay
        End If
    Next clsEmployee
   
    NetPay = dReturn
   
End Property

Because I don’t have a NetPay property in CCheck, I have two procedures that don’t compile. I don’t necessarily fix the next compile error that comes up. In this case, for example, I’ll go write the NetPay property in CCheck.

Public Property Get NetPay() As Double
   
    Dim clsCheckItem As CCheckItem
    Dim dReturn As Double
   
    For Each clsCheckItem In Me.CheckItems
        If clsCheckItem.PayrollItem.IsNetPay Then
            dReturn = dReturn + clsCheckItem.Amount
        End If
    Next clsCheckItem
   
    NetPay = dReturn
   
End Property

Following this rabbit down the hole, I write the IsNetPay property in CPayrollItem.

Public Property Get IsNetPay() As Boolean
   
    IsNetPay = (Len(Me.ExpenseAccount) = 0 Or Len(Me.LiabilityAccount) = 0)
   
End Property

If I have both an expense account and a liability account, I know it’s a company only expense. If either is missing, the offset must be cash, or net pay, so I include it. I may still have coded some properties that don’t yet exist, but I followed the main line through getting the net pay. At this point, I compile to find the next thing to do, and it’s global constant.

Much of this code is concatenating the XML tags with the data in between. I have a bunch of global string constants for those tags. They’re really not very interesting, but you can see them in the file if that’s your bag. After I create gsACHEDITOR, my next compile error points to GenerateACH in CEmployee.

Public Property Get GenerateACH(dtCheck As Date, ByRef lSeq As Long) As String
   
    Dim sReturn As String
    Dim clsCheck As CCheck
    Dim i As Long
   
    Set clsCheck = Me.CheckByDate(dtCheck)
   
    For i = 1 To Me.AccountCount
        lSeq = lSeq + 1
        sReturn = sReturn & gsACHEDTBL & vbNewLine & gsHOLD & vbNewLine
        sReturn = sReturn & gsBATCH & gsBATCHNUM & TagClose(gsBATCH, True) & vbNewLine
        sReturn = sReturn & gsNAME & Me.EmployeeName & TagClose(gsNAME, True) & vbNewLine
        sReturn = sReturn & gsACCT & Me.Accounts(i) & TagClose(gsACCT, True) & vbNewLine
        sReturn = sReturn & gsID & vbNewLine
        sReturn = sReturn & gsDISC & vbNewLine
        sReturn = sReturn & gsAMT & Format(Me.Amounts(i, clsCheck.NetPay), gsFMTDBL) & TagClose(gsAMT, True) & vbNewLine
        sReturn = sReturn & gsRTG & Me.Routings(i) & TagClose(gsRTG, True) & vbNewLine
        sReturn = sReturn & gsEFFDTE & Format(dtCheck, gsFMTDATE) & TagClose(gsEFFDTE, True) & vbNewLine
        sReturn = sReturn & gsTRANS & Me.AcctTypes(i) & TagClose(gsTRANS, True) & vbNewLine
        sReturn = sReturn & gsFREE & vbNewLine
        sReturn = sReturn & gsSEQ & lSeq & TagClose(gsSEQ, True) & vbNewLine
        sReturn = sReturn & TagClose(gsACHEDTBL) & vbNewLine
    Next i
   
    GenerateACH = sReturn
   
End Property

This creates a ton of compile errors for properties that don’t yet exist. While there is generally one XML tag block for each employee, there can be up to two. If the employee has two direct deposit accounts listed, I need a separate tag block for each one. I won’t go through every property that needs to be created, but I will talk a little about how I handle multiple accounts. First, I count them for my loop.

Public Property Get AccountCount() As Long
   
    Dim lReturn As Long
   
    If Len(Me.Account2) = 0 Then
        lReturn = 1
    Else
        lReturn = 2
    End If
   
    AccountCount = lReturn

End Property

If I have an Account2, it’s 2. If not, it’s 1. A little verbose, but very readable. The Account(lIndex) and Routings(lIndex) are pretty much the same.

Public Property Get Accounts(lIndex As Long) As String
   
    Dim sReturn As String
   
    If lIndex = 1 Then
        sReturn = Me.Account1
    Else
        sReturn = Me.Account2
    End If
   
    Accounts = sReturn
   
End Property

It’s set up to look like an array, but I know my limit is 2, so I just return one or the other. The Amounts property is a little different. I have to pass it the net pay so it can compute which portion goes to which account.

Public Property Get Amounts(lIndex As Long, dNetPay As Double) As Double
   
    Dim dReturn As Double
   
    If lIndex = 1 Then
        If Me.Amount1 > 1 Then ‘it’s a whole dollar amount
           dReturn = Me.Amount1
        Else
            dReturn = Round(dNetPay * Me.Amount1, 2)
        End If
    Else
        dReturn = dNetPay – Me.Amounts(1, dNetPay)
    End If
   
    Amounts = dReturn
   
End Property

Here’s the logic: An Amount1 of 1 means 100%. Less than one means a percentage of net pay. More than one means a fixed dollar amount. I don’t have Amount2 because that is always whatever is left over. If I’m looking for the first amount, I either take the whole dollar amount or multiply the percentage by net pay. If I’m looking for the second amount, I subtract the first amount from net pay.

I create whatever global constants I need and the compiler takes me back to CEmployees.GenerateACH and highlights the ACHTotalEditorTable property. I’ve taken my three main tag blocks at the end of the XML file (company totals, file specs, and batch totals) and put them into properties. This keeps the code cleaner and easier to read. They mostly just concatenate a bunch of constants. Nothing to see here.

One more thing to discuss. In my MUtilities standard module, I wrote this little gem

Public Function TagClose(sInput As String, Optional bTrim As Boolean = False) As String
   
    If bTrim Then
        TagClose = Replace(Trim(sInput), “< “, “</”, 1, 1)
    Else
        TagClose = Replace(sInput, “<”, “</”, 1, 1)
    End If
   
End Function

This allows my to take a tag like <batch> and turn it into </batch>. The bTrim argument is used because sometimes a closing tag goes at the end of the line where I don’t want leading spaces, and sometimes it goes on its own line where I do.

Now my code compiles and I run it and it works. Hurray. Next time, I’ll leverage all this work into creating a payroll review sheet. Most of the heavy lifting is done. I’ll just have to add a few more properties and methods to my classes.

This file contains NACHA3.xls and the XML file.

You can download NACHA3.zip

Leave a Reply