For a version in a page by itself (i.e., not in a scrollable iframe as below) visit http://www.tushar-mehta.com/publish_train/xl_vba_cases/13-03%20Shapes%20on%20a%20Map.shtml
Month: February 2011
Animal Songs
J-Walk compiled a list of songs with animals in the title. That data screams for analysis, doesn’t it?
The vertebrates (phylum Chordata) kicked ass and mammals (class Mammalia) dominated. But Birds (class Aves) were no slouch either, although many of those songs only references “bird”.
The most popular species is dog (Canis lupus). Oddly missing is human (Homo sapiens). The biggest contributing song was Pigs, Sheep, and Wolves by Paul Simon. The three titles that didn’t quite make it into kingdom Anamalia were Puff the Magic Dragon, Werewolves of London, and The Unicorn Song. I let the Monkees in as primates against my better judgment.
Mammals with only one entry: skunk, weasel, walrus, raccoon, bear, llama, reindeer, beaver, muskrat, mouse, kangaroo, sloth, and mole. Smallest phyla (at one) were Porifera (sponge), Mollusca (octopus), and Cridaria (coral).
Can you think of a better use of Excel than this?
You can download AnimalSongs.zip
NACHA Files Bonus
Just when you thought the class module fun was over, it’s not. As long as I have all this class infrastructure, I can leverage that into making some reports. As you are no doubt aware, employees get pissed off when you make errors on their paychecks. Therefore, a review report prior to making the ACH file is in order.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Public Sub GenerateReview() Dim vaWrite As Variant Dim rWrite As Range Dim vaTitles As Variant Set gshData = wshChecks wshReview.UsedRange.ClearContents vaTitles = Array("Employee", "Gross Pay", "Fed WH", "State WH", "Deductions", "Net Pay", "ER Tax", "ER Cont") FillClasses gshData wshReview.Range("a1").Resize(1, UBound(vaTitles) + 1).Value = vaTitles vaWrite = gclsEmployees.WriteReview(#1/21/2011#) Set rWrite = wshReview.Range("A2").Resize(UBound(vaWrite, 1), UBound(vaWrite, 2)) rWrite.Value = vaWrite End Sub |
There are couple of constructs in this code that I use quite a bit. I put the column headers in an array and write that array to a range. I like how it lists the column headers on one line from left to right. It makes adding or removing columns easy to maintain.
The other construct is to create a property that returns an array, resize a range based on that array, and write the array to that resized range. If I change my property, the only other code I need to change is vaTitles. Everything else is dynamic.
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 |
Public Property Get WriteReview(dtCheck As Date) As Variant Dim aReturn() As Variant Dim clsEmployee As CEmployee Dim clsCheck As CCheck Dim i As Long ReDim aReturn(1 To Me.CheckCount(dtCheck), 1 To 8) For Each clsEmployee In Me Set clsCheck = clsEmployee.CheckByDate(dtCheck) If Not clsCheck Is Nothing Then i = i + 1 aReturn(i, 1) = clsEmployee.EmployeeName aReturn(i, 2) = clsCheck.GrossPay aReturn(i, 3) = clsCheck.FederalWH aReturn(i, 4) = clsCheck.StateWH aReturn(i, 5) = clsCheck.Deductions aReturn(i, 6) = clsCheck.NetPay aReturn(i, 7) = clsCheck.CompanyTaxes aReturn(i, 8) = clsCheck.CompanyContributions End If Next clsEmployee WriteReview = aReturn End Property |
Clean and easy to read. Of course I have to write a bunch of properties to make it work. Let’s look at one of them. In CCheck
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Public Property Get GrossPay() As Double Dim clsCheckItem As CCheckItem Dim dReturn As Double For Each clsCheckItem In Me.CheckItems If clsCheckItem.PayrollItem.IsGrossPay Then dReturn = dReturn + clsCheckItem.Amount End If Next clsCheckItem GrossPay = dReturn End Property |
Most of the other properties are structured just like this. I loop through the CheckItems, make sure they apply (in this case via IsGrossPay), and add up the Amount properties. In CPayrollItem
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 |
Public Property Get IsGrossPay() As Boolean IsGrossPay = Me.IsBonus Or Me.IsWages Or Me.IsCommission End Property Public Property Get IsBonus() As Boolean Const sBONUS As String = "Bonus" IsBonus = Me.ItemName = sBONUS End Property Public Property Get IsCommission() As Boolean Const sCOM As String = "Commission" IsCommission = Me.ItemName = sCOM End Property Public Property Get IsWages() As Boolean Const sSALARY As String = "Salary" IsWages = InStr(1, Me.ItemName, sSALARY) > 0 End Property |
Most of my Is* properties either check the name of the PayrollItem or check for the existence of ExpenseAccount or LiabilityAccount. This is not an optimal way to do it. I’d rather have this data in the raw data and read it in as a property. If it was in the PayrollItem table, it would be easier to maintain. In the project that inspired this example, I didn’t have control over the raw data, so I had to make due. If a PayrollItem that should be considered “wages” doesn’t have the word “Salary” in its name, the code breaks. If I were doing it again, I might look for a different way to handle this part.
After I write all the properties I referenced in WriteReview and all the Is* properties in CPayrollItem, my code compiles, runs, and produces this.
Using class modules, my code is clean, easy to read, self documenting, and easy to modify if the situation warrants. If my employee, checks, and/or payroll item data moves from Excel tables to an Access database, I only have to change my Fill methods to fill the classes from a different source. Alternatively, if the format of my output (ACH file or Payroll reivew) changes, I only have to change the properties and procedures that generate the output. The classes act as a wall between the input and output. When one changes, the other is unaffected.
You can download NACHA4.zip
Reasonable Contract Terms
Several years ago, a potential client contacted me to work on a charting add-in. In the contract the client wanted me to sign was a clause that I would not create any chart that I created for this company and that I would never use any code that I used in creating its add-in.
Given that Excel has only a handful of chart types, I concluded that the first clause was unreasonable. The second was as bad, if not worse.
Depending on how one interpreted the code clause, would I be barred from using the Charts.Add method? Or a If statement? After all, there aren’t all that many ways to programmatically create a chart to visually represent data in a worksheet range.
A couple of years later I happened to visit the company’s website and discovered that the product was available for sale. Among the many charts were the classic BCG Matrix Model Chart and the Marimekko chart. I couldn’t help but wonder who agreed that s/he would never ever create any of the charts in the add-in. Not to mention that, strictly speaking, any Excel chart is really nothing more than one of the basic chart types, or a combination of the basic chart types, formatted appropriately. So, is this developer barred from creating a XY Scatter chart, a Bubble chart, and a Column Chart? I imagine if one went through all the charts in the add-in one would be barred from creating any chart in Excel! {grin}
Since then, I’ve worked with companies large and small including one of the world’s largest financial news provider, one of the largest, if not the largest, U.S. retail brokerage, a public utility company, several large regional health care providers and many smaller companies ranging from 10 employees to, oh, several hundred employees. And, I have not had a problem signing the contract, if any, that these companies have required.
Until a couple of weeks ago. A potential client contacted me about some work it wanted done. There were two clauses in particular that reminded me of the experience from all those years ago.
In the quotes from the contract below, I’ve replaced the company name by Cn (or Company name).
Non-Disclosure, Non-Complete and Confidentiality.
Each party acknowledges that it and its employees or agents may, in the course of this Agreement, be exposed to or acquire information which is proprietary to or confidential to the other party. Each party agrees to hold such information in strict confidence and not to disclose any such information to any third parties. Each party agrees that they will not engage in direct work with the other party’s clients.
Spelling and grammatical errors apart, I realize the intent of the last sentence was probably that we not poach the others clients. But, a literal interpretation would require something very different. If a company, say company ABC, is already a client of both Cn and myself, are we now both required to drop it as a client? {grin}
Intellectual Property.
Any writing or work of authorship, regardless of medium, created or developed by Cn or Tushar Mehta in the course of performing the Services under this Agreement and relating to any existing works owned by Cn or its clients shall not be deemed a “work for hire” and shall be owned solely and exclusively by Cn. To the extent any such work for any reason is determined not to be owned by Cn, Tushar Mehta hereby irrevocably assigns, transfers and conveys to Cn all of Tushar Mehta’s right, title, and interest in such Cn Work, including, but not limited to, all rights of patent, copyright, trade secret, know-how, and or other proprietary and associated rights in such Cn Work.
Again, I imagine the intent is very different than a literal interpretation of the clause. Cn probably wants an assurance that if it shared an existing model with me, any changes I made to it would still leave ownership with Cn. The place where I had a problem was what if I used code from my code library? Say, I drop my menu creator class into the Cn project? Or my equivalent of the now depracated Application.FileSearch? Or my version of the superset of the Range.Find method?
What happened? Well, I asked Cn if it was open to reviewing the clauses I had a problem with. A few days after my email, my contact informed me that Cn had found someone willing to sign the contract as-is.
What would you have done?
Maybe, having had a lawyer review a similar contract, you already know that my interpretation is overly paranoid?
Or, you know that the contract is unreasonable and therefore unenforceable?
Or, would you also have asked for a revised contract?
Or, just sign the contract and then ignore it when dealing with future clients? After all, how will Cn know what you do with another client?
Or, checked with a lawyer?
Or, something that I haven’t thought of myself?
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.
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
NACHA Files Part 2
In Part 1, I created the CEmployee and CPayrollItem classes and their parents. For CPayrollItem, I used ItemName as the property for the first column because I didn’t want to name it the same as the class name, PayrollItem. Next, I need to create the CCheck and CCheckItem classes.
For CCheckItem, I only need store an Amount. Every other property of the CheckItem will be inherited from the PayrollItem. So I add a class module, name it CCheckItem, and put this in it
1 2 |
Public CheckItemID As Long Public Amount As Double |
Convert those to properties and create a parent class. I have a one-to-one relationship between CheckItem and PayrollItem, so I don’t need a collection in CheckItem. I only need to refer to one instance of PayrollItem. That’s just another property that I add manually.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Private mclsPayrollItem As CPayrollItem Public Property Get PayrollItem() As CPayrollItem Set PayrollItem = mclsPayrollItem End Property Public Property Set PayrollItem(clsPayrollItem As CPayrollItem) Set mclsPayrollItem = clsPayrollItem End Property |
CheckItem is done. My Checks table isn’t really a table of checks, but a table of CheckItems.
In my CCheck class, I need to store the check date and that’s it. The name column will be a reference to the CEmployee class. The PayrollItem will be a reference to the CheckItem class. And the Amount column will come from CheckItem as well. So my CCheck code is pretty simple to start. After I convert my public variables and create a parent class, it looks like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Private mlCheckID As Long Private mdtCheckDate As Date Private mlParentPtr As Long Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (dest As Any, Source As Any, ByVal bytes As Long) Private mclsCheckItems As CCheckItems Public Property Get CheckDate() As Date: CheckDate = mdtCheckDate: End Property Public Property Let CheckDate(ByVal dtCheckDate As Date): mdtCheckDate = dtCheckDate: End Property Public Property Get CheckID() As Long: CheckID = mlCheckID: End Property Public Property Let CheckID(ByVal lCheckID As Long): mlCheckID = lCheckID: End Property Public Property Get Parent() As CChecks: Set Parent = ObjFromPtr(mlParentPtr): End Property Public Property Set Parent(obj As CChecks): mlParentPtr = ObjPtr(obj): End Property Private Function ObjFromPtr(ByVal pObj As Long) As Object Dim obj As Object CopyMemory obj, pObj, 4 Set ObjFromPtr = obj ' manually destroy the temporary object variable ' (if you omit this step you'll get a GPF!) CopyMemory obj, 0&, 4 End Function |
I’ve already created the relationship between CheckItem and PayrollItem. Now I need to create the other relationships. In CEmployee, I do this
1 2 3 4 5 6 7 |
Private mclsChecks As CChecks Private Sub Class_Initialize() Set mclsChecks = New CChecks End Sub |
And in CCheck
1 2 3 4 5 6 |
Private mclsCheckItems As CCheckItems Private Sub Class_Initialize() Set mclsCheckItems = New CCheckItems End Sub |
I know I’ll need some code to actually fill the instances, but I’ll let the code drive that rather than do it now. Finally for today, I need to fill the classes. I’ll create Fill methods in CEmployees and CPayrollItems. First, I’ll create two global variables in a standard module
1 2 |
Public gclsEmployees As CEmployees Public gclsPayrollItems As CPayrollItems |
In CPayrollItems, I create this Fill method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Public Sub Fill() Dim sh As Worksheet Dim rCell As Range Dim clsPayrollItem As CPayrollItem Set sh = wshPayrollItems For Each rCell In Intersect(sh.Columns(1), sh.UsedRange).Cells If rCell.Row > 1 Then If Not IsEmpty(rCell.Value) Then Set clsPayrollItem = New CPayrollItem With clsPayrollItem .ItemName = rCell.Value .ExpenseAccount = rCell.Offset(0, 1).Value .LiabilityAccount = rCell.Offset(0, 2).Value End With Me.Add clsPayrollItem End If End If Next rCell End Sub |
It simply loops through column A and adds a PayrollItem for each row. In CEmployees, I create this Fill method
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 |
Public Sub Fill() Dim sh As Worksheet Dim rCell As Range Dim clsEmployee As CEmployee Const lNETPAY As String = 1 Set sh = wshEmployees For Each rCell In sh.Range("A2", sh.Range("A2").End(xlDown)).Cells Set clsEmployee = New CEmployee With clsEmployee .EmployeeName = rCell.Value .SSN = rCell.Offset(0, 1).Value .Account1 = rCell.Offset(0, 2).Value .Routing1 = rCell.Offset(0, 3).Value .Type1 = rCell.Offset(0, 4).Value .Amount1 = rCell.Offset(0, 5).Value .Account2 = rCell.Offset(0, 6).Value .Routing2 = rCell.Offset(0, 7).Value .Type2 = rCell.Offset(0, 8).Value End With Me.Add clsEmployee Next rCell End Sub |
Same drill as CPayrollItem. My last class to fill is CChecks. I do that in a standard module
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 |
Sub FillClasses(sh As Worksheet) Dim rCell As Range Dim clsEmployee As CEmployee Dim clsCheck As CCheck Dim clsCheckItem As CCheckItem Set gclsPayrollItems = New CPayrollItems gclsPayrollItems.Fill Set gclsEmployees = New CEmployees gclsEmployees.Fill For Each rCell In Intersect(sh.UsedRange, sh.Columns(1)) If IsDate(rCell.Value) Then Set clsEmployee = gclsEmployees.EmployeeByName(rCell.Offset(0, 1).Value) Set clsCheck = clsEmployee.CheckByDate(rCell.Value, True) Set clsCheckItem = New CCheckItem With clsCheckItem Set .PayrollItem = gclsPayrollItems.PayrollItemByName(rCell.Offset(0, 2).Value) .Amount = rCell.Offset(0, 3).Value End With clsCheck.AddCheckItem clsCheckItem End If Next rCell End Sub |
At this point, the code no longer compiles. EmployeeByName, CheckByDate, PayrollItemByName, and AddCheckItem do not exist in their respective classes. I typed them because I knew what I wanted. I wanted to retrieve the employee by his name. I knew I would have to supply the name as an argument. So I typed the property call how I thought it would look, not worrying that there is no underlying property to support it. Now that I have code that doesn’t compile, I set about creating the underlying properties so that it will compile (and hopefully be functional).
I won’t go through all of these properties. The ‘ByName properties simply loop through a collection until it finds a match. I do want to show the CheckByDate property because I did something a little different. Because I can’t control the layout of my check data (It sort of comes from Quickbooks), I don’t have properly relational data. In other words, I don’t have a Checks table and a CheckItems table that are linked by a key. In CheckByDate, I added an additional Boolean argument that allows me to create an instance of the check if it doesn’t exist. As I move through the table, Elijah Robinson won’t have a check dated 1/7/11, so it’s created. At line 3 of my data, however, that check exists and I simply append the information.
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 |
Public Property Get CheckByDate(dtCheck As Date, Optional bCreate As Boolean = False) As CCheck Dim clsCheck As CCheck For Each clsCheck In Me.Checks If clsCheck.CheckDate = dtCheck Then Exit For Next clsCheck If bCreate Then If clsCheck Is Nothing Then Set clsCheck = New CCheck clsCheck.CheckDate = dtCheck Me.AddCheck clsCheck End If If clsCheck.CheckDate <> dtCheck Then Set clsCheck = New CCheck clsCheck.CheckDate = dtCheck Me.AddCheck clsCheck End If End If Set CheckByDate = clsCheck End Property |
If bCreate is True and the check was not found, a new check is created and added to the Employee class. Note that bCreate is optional and defaults to False. That way I can use it as I would a similar property that didn’t have that option and get Nothing back if it doesn’t exist. At this point in the code, I know checks won’t exist because I’m creating them.
By writing CheckByDate, I now have another layer of code that doesn’t compile. I still have property calls in FillClasses that I haven’t written yet. Put now I have property calls in CheckByDate that I haven’t written. I wrote Me.Checks and Me.AddCheck fully aware that they don’t exist. This is where the finsih-to-start model gets a little hairy. I start to feel uneasy because I can’t hold all of this pending information in my brain at once. I have to trust that the compiler will tell me when I’m done.
I write the Checks property, which simply returns the mclsChecks variable I defined earlier. Then I compile. The next error is AddCheck, so I write it.
1 2 3 4 5 |
Public Sub AddCheck(clsCheck As CCheck) mclsChecks.Add clsCheck End Sub |
My CreateParent utility already included an add method, so this one is OK. I recompile and it takes me back to FillClasses and tells me there is no such thing as PayrollItemByName. I write it and recompile. AddCheckItem is the next victim. Once that is written, my code compiles and all is right with the world. When my code compiles, that’s my trigger to write a test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Sub Test_FillClasses() Dim clsEmployee As CEmployee Dim clsCheck As CCheck Dim clsCheckItem As CCheckItem FillClasses wshChecks For Each clsEmployee In gclsEmployees For Each clsCheck In clsEmployee.Checks Debug.Print clsEmployee.EmployeeName, clsCheck.CheckDate For Each clsCheckItem In clsCheck.CheckItems With clsCheckItem Debug.Print .PayrollItem.ItemName, .Amount End With Next clsCheckItem Next clsCheck Next clsEmployee End Sub |
>Now I’m back to having code that doesn’t compile. My Check class doesn’t have a CheckItems property because I haven’t needed it yet. All it does is return a private variable, so I write it. Now my test code works and I can move on to the next high level procedure.
In Part 1, I defined my data structure and set up some basic class modules. Here, I create additional class module and link them together (mostly). When I write my Fill procedures, I call properties and methods that I need, regardless of whether they exist. While this leaves me with code that won’t compile for an extended period of time, it ensures that I don’t write any properties or methods that I don’t need.
Next time, I’ll write my top level procedure to generate the XML file. Then I’ll keep writing properties and methods until the code compiles.
If you want to see the finished product
You can download NACHA.zip
If you want to see the code at this point in the tutorial
You can download NACHA2.zip
NACHA Files Part 1
There is a product that some banks use, like my bank, called Premier ACH. It’s hosted on the bank’s website. It allows you to enter the data for an ACH transactions (Automated Clearing House, think direct deposit) and creates a properly formatted NACHA file for you. You can submit a NACHA file to your bank and they will initiate an ACH transaction for you. It’s a two step process: create a wrk file and turn it into a NACHA file. The wrk file is an XML file.
The goal of this series of posts is to turn Excel data into a properly formatted wrk file. The Excel data is fake for this example, but it’s meant to approximate the format you might get when you export certain reports out of Quickbooks. It isn’t exact because creating relational fake data is a real pain. But it’s close and it will teach you the necessary skills to manipulate the data in…wait for it…custom class modules. Who didn’t see that coming?
A sample of the Excel data:
And a sample of the XML (wrk) file:
Here are the basics steps I took:
- Identify objects and relationships
- Create class modules
- Write code to fill classes
- Write code to create XML file
- Augment class modules until it compiles
- Bonus: Create a payroll review sheet
Lately, I write most of my code in this fashion. Once the basic class modules are created, I write the main procedure. The main procedure usually includes one or two methods of my custom objects and provides the framework for what I want to accomplish. There are a couple of advantages, and one big disadvantage, to coding this way. I’m not advocating it as great method, I’m just saying it’s how I’ve been working lately. So far, I like it.
The first major advantage is the aforementioned framework it provides. By coding from finish to start, I know exactly what my result should look like before I get into the details. The other advantage is I never code anything I don’t need. Like test driven development, this method forces me to only write properties and methods in my classes that I need. A short disclaimer: This example was abstracted from a larger, more complex application, so you may see an occasional unused property.
That big disadvantage? The code doesn’t compile until I’ve written a boat load. Some, like me, have advocated that you should never be more than a few lines from code that compiles. This method could not be more contrary to that advice.
Let’s get on with it. I have Employees, PayrollItems, and Checks. I think everyone knows what employees and checks are, so I won’t belabor those. A PayrollItem is an entity that controls how a check applies to the general ledger. Examples of PayrollItems are Salary, Federal Withholding, and State Unemployment. I need one more object to link Checks and PayrollItems. For example, Salary is a payroll item, but the amount assigned to Salary will certainly vary by employee and may even vary by check. To accommodate that, I create a CheckItem object. The CheckItem will hold the variable data of the PayrollItem for a particular check. As for relationships
- Employee has many Checks
- Check has many CheckItems
- CheckItem has one PayrollItem
Let’s start by building CEmployee. First, we’ll look at the data
I have five employees with a name and social security number. Each employee has one or two direct deposit accounts. I insert a class into my project, name it CEmployee, and enter the columns as public variables.
1 2 3 4 5 6 7 8 9 10 11 |
Option Explicit Public EmployeeID As Long Public EmployeeName As String Public SSN As String Public Account1 As String Public Routing1 As String Public Type1 As String Public Amount1 As String Public Account2 As String Public Routing2 As String Public Type2 As String |
Then I run my Public to Private and Create Parent macros and get this
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
Private mlEmployeeID As Long Private msEmployeeName As String Private msSSN As String Private msAccount1 As String Private msRouting1 As String Private msType1 As String Private msAmount1 As String Private msAccount2 As String Private msRouting2 As String Private msType2 As String Private mlParentPtr As Long Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (dest As Any, Source As Any, ByVal bytes As Long) Private mclsChecks As CChecks Public Property Get Type2() As String: Type2 = msType2: End Property Public Property Let Type2(ByVal sType2 As String): msType2 = sType2: End Property Public Property Get Routing2() As String: Routing2 = msRouting2: End Property Public Property Let Routing2(ByVal sRouting2 As String): msRouting2 = sRouting2: End Property Public Property Get Account2() As String: Account2 = msAccount2: End Property Public Property Let Account2(ByVal sAccount2 As String): msAccount2 = sAccount2: End Property Public Property Get Amount1() As String: Amount1 = msAmount1: End Property Public Property Let Amount1(ByVal sAmount1 As String): msAmount1 = sAmount1: End Property Public Property Get Type1() As String: Type1 = msType1: End Property Public Property Let Type1(ByVal sType1 As String): msType1 = sType1: End Property Public Property Get Routing1() As String: Routing1 = msRouting1: End Property Public Property Let Routing1(ByVal sRouting1 As String): msRouting1 = sRouting1: End Property Public Property Get Account1() As String: Account1 = msAccount1: End Property Public Property Let Account1(ByVal sAccount1 As String): msAccount1 = sAccount1: End Property Public Property Get SSN() As String: SSN = msSSN: End Property Public Property Let SSN(ByVal sSSN As String): msSSN = sSSN: End Property Public Property Get EmployeeName() As String: EmployeeName = msEmployeeName: End Property Public Property Let EmployeeName(ByVal sEmployeeName As String): msEmployeeName = sEmployeeName: End Property Public Property Get EmployeeID() As Long: EmployeeID = mlEmployeeID: End Property Public Property Let EmployeeID(ByVal lEmployeeID As Long): mlEmployeeID = lEmployeeID: End Property Public Property Get Parent() As CEmployees: Set Parent = ObjFromPtr(mlParentPtr): End Property Public Property Set Parent(obj As CEmployees): mlParentPtr = ObjPtr(obj): End Property Private Function ObjFromPtr(ByVal pObj As Long) As Object Dim obj As Object CopyMemory obj, pObj, 4 Set ObjFromPtr = obj ' manually destroy the temporary object variable ' (if you omit this step you'll get a GPF!) CopyMemory obj, 0&, 4 End Function |
I do the same thing for my PayrollItems table. It looks like this
Next time, I’ll set up the CheckItem and Check classes and fill them all. If you can’t wait that long…
You can download NACHA.zip
Recording a Sort Macro in 2003 vs 2007
Excel 2007 records the sort operation differently than Excel 2003. In 2007, the range is specified in the SetRange method while in 2003 the Selection object is used. Take this simple checkbook workbook.
If you record a macro to sort on check number in 2003, you get something like this
‘
‘ Macro3 Macro
‘ Macro recorded 2/20/2011 by Dick Kusleika
‘
‘
Range(“A1”).Select
Selection.CurrentRegion.Select
Selection.Sort Key1:=Range(“B2”), Order1:=xlAscending, Header:=xlGuess, _
OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, _
DataOption1:=xlSortNormal
End Sub
Cell A1 is selected, then the current region by using the Goto – Special dialog (F5). The sort is done on the selected range. Contrast that with a macro recorded in 2007.
‘
‘ Macro1 Macro
‘
‘
Range(“A1”).Select
Selection.CurrentRegion.Select
ActiveWorkbook.Worksheets(“Sheet1”).Sort.SortFields.Clear
ActiveWorkbook.Worksheets(“Sheet1”).Sort.SortFields.Add Key:=Range(“B2:B12”) _
, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets(“Sheet1”).Sort
.SetRange Range(“A1:E12”)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
In this version, the range A1:E12 is hard-coded. When a new row is added, the 2003 code works the same and the 2007 code fails.
Like many readers of DDoE, I don’t accept recorded code. I only use it to discover objects, properties, and methods necessary. This is a special case. This workbook is used to instruct people who are not just new to Excel, but new to computers in general. Having them open the VBE, much less edit code, is out of the question.
I considered using Lists (Tables in 2007), but they act so differently in the two versions that I ruled it out. Ultimately I want to demonstrate recording a macro that sorts on check number and another macro that resorts on date. Any ideas on how I can accomplish this without editing the code?