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.
1 2 3 4 5 6 7 8 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
1 2 3 4 5 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
1 2 3 4 5 6 7 8 9 |
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
Posting code? Use <pre> tags for VBA and <code> tags for inline.