We begin by thinking about the transactions that represent the use cases. Figure 27-1 shows that we represent transactions as an interface named transaction, which has a method named Execute(). This is, of course, the COMMAND pattern. The implementation of the transaction class is shown in Listing 27-1. Figure 27-1. Transaction interface
Listing 27-1. transaction.cs
Adding EmployeesFigure 27-2 shows a potential structure for the transactions that add employees. Note that it is within these transactions that the employees' payment schedule is associated with their payment classification. This is appropriate, since the transactions are contrivances instead of part of the core model. Thus, for example, the core model is unaware that hourly employess are paid weekly. The association between payment classificaton and payment schedule is merely part of one of the peripheral contrivances and can be changed at any time. For example, we could easily add a transaction that allows us to change employee schedules. Figure 27-2. Static model of AddEmployeeTransactionThis decision conforms nicely to OCP and SRP. It is the responsibility of the transactions, not the core model, to specify the association between payment type and payment schedule. What's more, that association can be changed without changing the core model. Note, too, that the default payment method is to hold the paycheck with the paymaster. If an employee wants a different payment method, it must be changed with the appropriate ChgEmp TRansaction. As usual, we begin writing code by writing tests first. The test case in Listing 27-2 shows that the AddSalariedTransaction is working correctly. The code to follow will make that test case pass. Listing 27-2. PayrollTest.TestAddSalariedEmployee
The payroll databaseThe AddEmployeeTransaction class uses a class called PayrollDatabase. For the moment, this class maintains all the existing Employee objects in a Hashtable that is keyed by empID. The class also maintains a Hashtable that maps union memberIDs to empIDs. We'll figure out how to make the contents persistent later. The structure for this class appears in Figure 27-3. PayrollDatabase is an example of the FACADE pattern. Figure 27-3. Static structure of PayrollDatabase
Listing 27-3 shows a rudimentary implementation of the PayrollDatabase. This implementation is meant to help us with our initial test cases. It does not yet contain the hash table that maps member IDs to Employee instances. Listing 27-3. PayrollDatabase.cs
In general, I consider database implementations to be details. Decisions about those details should be deferred as long as possible. Whether this particular database will be implemented with a relational database management system (RDBMS), or flat files, or an object-oriented database management system (OODBMS), is irrelevant at this point. Right now, I'm simply interested in creating the API that will provide database services to the rest of the application. I'll find appropriate implementations for the database later. Deferring details about the database is an uncommon but very rewarding practice. Database decisions can usually wait until we have much more knowledge about the software and its needs. By waiting, we avoid the problem of putting too much infrastructure into the database. Rather, we implement only enough database facility for the current needs of the application. Using Template Method to add employeesFigure 27-4 shows the dynamic model for adding an employee. Note that the AddEmployeeTransaction object sends messages to itself in order to get the appropriate PaymentClassification and PaymentSchedule objects. These messages are implemented in the derivatives of the AddEmployeeTransaction class. This is an application of the TEMPLATE METHOD pattern. Figure 27-4. Dynamic model for adding an employeeListing 27-4 shows the implementation of the TEMPLATE METHOD pattern in the AddEmployeeTransaction class. This class implements the Execute() method to call two pure virtual functions that will be implemented by derivatives. These functions, MakeSchedule() and MakeClassification(), return the PaymentSchedule and PaymentClassification objects that the newly created Employee needs. The Execute() method then binds these objects to the Employee and saves the Employee in the PayrollDatabase. Two things are of particular interest here. First, when the TEMPLATE METHOD pattern is applied, as it is here, for the sole purpose of creating objects, it goes by the name FACTORY METHOD. Second, it is conventional for the creation methods in the FACTORY METHOD pattern to be named MakeXXX(). I realized both of these issues while I was writing the code, and that is why the method names differ between the code and the diagram. Should I have gone back and changed the diagram? I didn't see the need in this case. I don't intend for that diagram to be used as a reference by anyone else. Indeed, if this were a real project, that diagram would have been drawn on a whiteboard and would probably now be on the verge of being erased. Listing 27-4. AddEmployeeTransaction.cs
Listing 27-5 shows the implementation of the AddSalariedEmployee class. This class derives from AddEmployeeTransaction and implements the MakeSchedule() and MakeClassification() methods to pass back the appropriate objects to AddEmployeeTransaction.Execute(). Listing 27-5. AddSalariedEmployee.cs
The AddHourlyEmployee and AddCommissionedEmployee are left as exercises for you. Remember to write your test cases first. Deleting EmployeesFigures 27-5 and 27-6 present the static and dynamic models for the transactions that delete employees. Listing 27-6 shows the test case for deleting an employee. Listing 27-7 shows the implementation of DeleteEmployeeTransaction. This is a very typical implementation of the COMMAND pattern. The constructor stores the data that the Execute() method eventually operates on. Figure 27-5. Static model for DeleteEmployee transaction
Figure 27-6. Dynamic model for DeleteEmployee TRansaction
Listing 27-6. PayrollTest.DeleteEmployee
Listing 27-7. DeleteEmployeeTransaction.cs
By now, you have noticed that the PayrollDatabase provides static access to its fields. In effect, PayrollDatabase.employees is a global variable. For decades, textbooks and teachers have been discouraging the use of global variables, with good reason. Still, global variables are not intrinsically evil or harmful. This particular situation is an ideal choice for a global variable. There will ever be only one instance of the PayrollDatabase methods and variables, and it needs to be known by a wide audience. You might think that this could be better accomplished by using the SINGLETON or MONOSTATE patterns. It is true that these would serve the purpose. However, they do so by using global variables themselves. A SINGLETON or a MONOSTATE is, by definition, a global entity. In this case, I felt that a SINGLETON or a MONOSTATE would smell of needless complexity. It's easier to simply keep the database global. Time Cards, Sales Receipts, and Service ChargesFigure 27-7 shows the static structure for the transaction that posts time cards to employees. Figure 27-8 shows the dynamic model. The basic idea is that the transaction gets the Employee object from the PayrollDatabase, asks the Employee for its PaymentClassification object, and then creates and adds a TimeCard object to that PaymentClassification. Figure 27-7. Static structure of TimeCardTransaction
Figure 27-8. Dynamic model for posting a TimeCardNote that we cannot add TimeCard objects to general PaymentClassification objects; we can add them only to HourlyClassification objects. This implies that we must downcast the PaymentClassification object received from the Employee object to an HourlyClassification object. This is a good use for the as operator in C# (see Listing 27-10). Listing 27-8 shows one of the test cases that verifies that time cards can be added to hourly employees. This test code simply creates an hourly employee and adds it to the database. Then it creates a TimeCardTransaction, invokes Execute(), and checks whether the employee's HourlyClassification contains the appropriate TimeCard. Listing 27-8. PayrollTest.TestTimeCardTransaction
Listing 27-9 shows the implementation of the TimeCard class. There's not much to this class right now. It's simply a data class. Listing 27-9. TimeCard.cs
Listing 27-10 shows the implementation of the TimeCardTransaction class. Note the use of InvalidOperationExceptions. This is not particularly good long-term practice but suffices this early in development. After we get some idea of what the exceptions ought to be, we can come back and create meaningful exception classes. Listing 27-10. TimeCardTransaction.cs
Figures 27-9 and 27-10 show a similar design for the transaction that posts sales receipts to a commissioned employee. I've left the implementation of these classes as an exercise. Figure 27-9. Static model for SalesReceiptTransaction
Figure 27-10. Dynamic model for SalesReceiptTransactionFigures 27-11 and 27-12 show the design for the transaction that posts service charges to union members. These designs point out a mismatch between the transaction model and the core model that we have created. Our core Employee object can be affiliated with many different organizations, but the transaction model assumes that any affiliation must be a union affiliation. Thus, the transaction model provides no way to identify a particular kind of affiliation. Instead, it simply assumes that if we are posting a service charge, the employee has a union affiliation. Figure 27-11. Static model for ServiceChargeTransaction
Figure 27-12. Dynamic model for ServiceChargeTransactionThe dynamic model addresses this dilemma by searching the set of Affiliation objects contained by the Employee object for a UnionAffiliation object. The model then adds the ServiceCharge object to that UnionAffiliation. Listing 27-11 shows the test case for the ServiceChargeTransaction. It simply creates an hourly employee, adds a UnionAffiliation to it, makes sure that the appropriate member ID is registered with the PayrollDatabase, creates and executes a ServiceChargeTransaction, and, finally, makes sure that the appropriate ServiceCharge was indeed added to Employee's UnionAffiliation. Listing 27-11. PayrollTest.AddServiceCharge
When I drew the UML in Figure 27-12, I thought that replacing NoAffiliation with a list of affiliations was a better design. I thought it was more flexible and less complex. After all, I could add new affiliations any time I wanted, and I didn't have to create the NoAffiliation class. However, when writing the test case in Listing 27-11, I realized that setting the Affiliation property on Employee was better than calling AddAffiliation. After all, the requirements do not ask that an employee have more than one Affiliation, so there is no need to use a cast to select from potentially many kinds. Doing so would be more complex than necessary. This is an example of why doing too much UML without verifying it in code can be dangerous. The code can tell you things about your design that the UML cannot. Here, I was putting structures into the UML that weren't needed. Maybe one day they'd come in handy, but they have to be maintained between now and then. The cost of that maintenance may not be worth the benefit. In this case, even though the cost of maintaining the downcast is relatively slight, I'm not going to use it; it's much simpler to implement without a list of Affiliation objects. So I'll keep the NULL OBJECT pattern in place with the NoAffiliation class. Listing 27-12 shows the implementation of the ServiceChargeTransaction. It is indeed much simpler without the loop looking for UnionAffiliation objects. It simply gets the Employee from the database, downcasts its Affillation to a UnionAffilliation, and adds the ServiceCharge to it. Listing 27-12. ServiceChargeTransaction.cs
Changing EmployeesFigure 27-13 show the static structure for the transactions that change the attributes of an employee. This structure is easily derived from use case 6. All the transactions take an EmpID argument, so we can create a top-level base class called Change-EmployeeTransaction. Below this base class are the classes that change single attributes, such as ChangeNameTransaction and ChangeAddressTransaction. The transactions that change classifications have a commonality of purpose in that they all modify the same field of the Employee object. Thus, they can be grouped together under an abstract base, ChangeClassificationTransaction. The same is true of the transactions that change the payment and the affiliations. This can be seen by the structure of Change-MethodTransaction and ChangeAffiliationTransaction. Figure 27-13. Static model for ChangeEmployeeTransaction
Figure 27-14 shows the dynamic model for all the change transactions. Again, we see the TEMPLATE METHOD pattern in use. In every case, the Employee object corresponding to the EmpID must be retrieved from the PayrollDatabase. Thus, the Execute function of ChangeEmployeeTransaction implements this behavior and then sends the Change message to itself. This method will be declared as virtual and implemented in the derivatives, as shown in Figures 27-15 and 27-16. Figure 27-14. Dynamic model for ChangeEmployeeTransaction
Figure 27-15. Dynamic model for ChangeNameTransaction
Figure 27-16. Dynamic model for ChangeAddressTransaction
Listing 27-13 shows the test case for the ChangeNameTransaction. This simple test case uses the AddHourlyEmployee transaction to create an hourly employee named Bill. It then creates and executes a ChangeNameTransaction that should change the employee's name to Bob. Finally, it fetches the Employee instance from the Payroll-Database and verifies that the name has been changed. Listing 27-13. PayrollTest.TestChangeNameTransaction()
Listing 27-14 shows the implementation of the abstract base class ChangeEmployeeTransaction. The structure of the TEMPLATE METHOD pattern is clearly in evidence. The Execute() method simply reads the appropriate Employee instance from the PayrollDatabase and, if successful, invokes the abstract Change() method. Listing 27-14. ChangeEmployeeTransaction.cs
Listing 27-15 shows the implementation of the ChangeNameTransaction. The second half of the TEMPLATE METHOD can easily be seen. The Change() method is implemented to change the name of the Employee argument. The structure of the ChangeAddressTransaction is very similar and is left as an exercise. Listing 27-15. ChangeNameTransaction.cs
Changing ClassificationFigure 27-17 shows how the hierarchy beneath Change-ClassificationTransaction is envisioned. The TEMPLATE METHOD pattern is used yet again. All these transactions must create a new PaymentClassification object and then hand it to the Employee object. This is accomplished by sending the GetClassification message to itself. This abstract method is implemented in each of the classes derived from ChangeClassificationTransaction, as shown in Figures 27-18 through Figure 27-20. Figure 27-17. Dynamic model of ChangeClassificationTransactionFigure 27-18. Dynamic model of ChangeHourlyTransactionFigure 27-19. Dynamic model of ChangeSalariedTransactionFigure 27-20. Dynamic Model of ChangeCommissionedTransactionListing 27-16 shows the test case for the ChangeHourlyTransaction. The test case uses an AddCommissionedEmployee TRansaction to create a commissioned employee and then creates a ChangeHourlyTransaction and executes it. The transaction fetches the changed employee and verifies that its PaymentClassification is an Hourly-Classification with the appropriate hourly rate and that its PaymentSchedule is a WeeklySchedule. Listing 27-16. PayrollTest.TestChangeHourlyTransaction()
Listing 27-17 shows the implementation of the abstract base class ChangeClassificationTransaction. Once again, the TEMPLATE METHOD pattern is easy to pick out. The Change() method invokes the two abstract getters for the, Classification and Schedule properties and uses the values from these properties to set the classification and schedule of the Employee. Listing 27-17. ChangeClassificationTransaction.cs
The decision to use properties instead of get functions was made as the code was being written. Again, we see the tension between the diagrams and the code. Listing 27-18 shows the implementation of the ChangeHourlyTransaction class. This class completes the TEMPLATE METHOD pattern by implementing the getters for the Classification and Schedule properties that it inherited from Change-ClassificationTransaction. The class implements the Classification getter to return a newly created HourlyClassification and implements the Schedule getter to return a newly created WeeklySchedule. Listing 27-18. ChangeHourlyTransaction.cs
As always, the ChangeSalariedTransaction and ChangeCommissionedTransaction are left as an exercise. A similar mechanism is used for the implementation of ChangeMethod-Transaction. The abstract Method property is used to select the proper derivative of PaymentMethod, which is then handed to the Employee object (see Figures 27-21 through 27-24). Figure 27-21. Dynamic model of ChangeMethodTransaction
Figure 27-22. Dynamic model of ChangeDirectTransaction
Figure 27-23. Dynamic model of ChangeMailTransaction
Figure 27-24. Dynamic model of ChangeHoldTransaction
The implementation of these classes turned out to be straightforward and unsurprising. They too are left as an exercise. Figure 27-25 shows the implementation of the ChangeAffiliationTransaction. Once again, we use the TEMPLATE METHOD pattern to select the Affiliation derivative that should be handed to the Employee object. (See Figures 27-26 through 27-28). Figure 27-25. Dynamic model of ChangeAffiliationTransaction
Figure 27-26. Dynamic model of ChangeMemberTransaction
Figure 27-27. Dynamic model of ChangeUnaffiliatedTransaction
What Was I Smoking?I got quite a surprise when I went to implement this design. Look closely at the dynamic diagrams for the affiliation transactions. Can you spot the problem? As always, I began the implementation by writing the test case for ChangeMemberTransaction. You can see this test case in Listing 27-19. The test case starts out straightforward enough. It creates an hourly employee named Bill and then creates and executes a ChangeMemberTransaction to put Bill in the union. Then it checks to see that Bill has a UnionAffiliation bound to him and that the UnionAffiliation has the right dues rate. Listing 27-19. PayrollTest.ChangeUnionMember()
The surprise is hidden in the last few lines of the test case. Those lines make sure that the PayrollDatabase has recorded Bill's membership in the union. Nothing in the existing UML diagrams makes sure that this happens. The UML is concerned only with the appropriate Affiliation derivative being bound to the Employee. I didn't notice the deficit at all. Did you? I merrily coded the transactions as per the diagrams and then watched the unit test fail. Once the failure occurred, it was obvious what I had neglected. What was not obvious was the solution to the problem. How do I get the membership to be recorded by ChangeMemberTransaction but erased by ChangeUnaffiliatedTransaction? The answer was to add to ChangeAffiliationTransaction another abstract method, named RecordMembership(Employee). This function is implemented in ChangeMemberTransaction to bind the memberId to the Employee instance. In the ChangeUnaffiliatedTransaction, it is implemented to erase the membership record. Listing 27-20 shows the resulting implementation of the abstract base class ChangeAffiliationTransaction. Again, the use of the TEMPLATE METHOD pattern is obvious. Listing 27-20. ChangeAffiliationTransaction.cs
Listing 27-21 shows the implementation of ChangeMemberTransaction. This is not particularly complicated or interesting. On the other hand, the implementation of ChangeUnaffiliatedTransaction in Listing 27-22 is a bit more substantial. The RecordMembership function has to decide whether the current employee is a union member. If so, it gets the memberId from the UnionAffiliation and erases the membership record. Listing 27-21. ChangeMemberTransaction.cs
Listing 27-22. ChangeUnaffiliatedTransaction.cs
I can't say that I'm very pleased with this design. It bothers me that the ChangeUnaffiliatedTransaction must know about UnionAffiliation. I could solve this by putting RecordMembership and EraseMembership abstract methods in the Affiliation class. However, this would force UnionAffiliation and NoAffiliation to know about the PayrollDatabase. And I'm not very happy about that, either.[1]
Still, the implementation as it stands is pretty simple and violates OCP only slightly. The nice thing is that very few modules in the system know about ChangeUnaffiliatedTransaction, so its extra dependencies aren't doing very much harm. Paying EmployeesFinally, it is time to consider the transaction that is at the root of this application: the transaction that instructs the system to pay the appropriate employees. Figure 27-28 shows the static structure of the PaydayTransaction class. Figure 27-29 and Figure 27-30 describe the dynamic behavior. Figure 27-28. Static model of PaydayTransaction
Figure 27-29. Dynamic model for PaydayTransaction
Figure 27-30. Dynamic model scenario: "Payday is not today."
The dynamic models express a great deal of polymorphic behavior. The algorithm used by the CalculatePay message depends on the kind of PaymentClassification that the Employee object contains. The algorithm used to determine whether a date is a payday depends on the kind of PaymentSchedule that the Employee contains. The algorithm used to send the payment to the Employee depends on the type of the PaymentMethod object. This high degree of abstraction allows the algorithms to be closed against the addition of new kinds of payment classifications, schedules, affiliations, or payment methods. The algorithms depicted in Figure 27-31 and Figure 27-32 introduce the concept of posting. After the correct pay amount has been calculated and sent to the Employee, the payment is posted; that is, the records involved in the payment are updated. Thus, we can define the CalculatePay method as calculating the pay from the last posting until the specified date. Figure 27-31. Dynamic model scenario: "Payday is today."Figure 27-32. Dynamic model scenario: Posting payment
Developers and business decisionsWhere did this notion of posting come from? It certainly wasn't mentioned in the user stories or the use cases. As it happens, I cooked it up as a way to solve a problem that I perceived. I was concerned that the Payday method might be called multiple times with the same date or with a date in the same pay period, so I wanted to make sure that the employee was not paid more than once. I did this on my own initiative, without asking my customer. It just seemed the right thing to do. In effect, I made a business decision, deciding that multiple runs of the payroll program should produce different results. I should have asked my customer or project manager about this, since they might have very different ideas. In checking with the customer,[2] I find that the idea of posting goes against his intent. The customer wants to be able to run the payroll system and then review the paychecks. If any of them are wrong, the customer wants to correct the payroll information and run the payroll program again. The customer tells me that I should never consider time cards or sales receipts for dates outside the current pay period.
So, we have to ditch the posting scheme. It seemed like a good idea at the time, but it was not what the customer wanted. Paying Salaried EmployeesThe two test cases in Listing 27-23 test whether a salaried employee is being paid appropriately. The first test case makes sure that the employee is paid on the last day of the month. The second test case makes sure that the employee is not paid if it is not the last day of the month. Listing 27-23. PayrollTest.PaySingleSalariedEmployee et al.
Listing 27-24 shows the Execute() function of PaydayTransaction. It iterates through all the Employee objects in the database, asking each employee if the date on this transaction is its pay date. If so, it creates a new paycheck for the employee and tells the employee to fill in its fields. Listing 27-24. PaydayTransaction.Execute()
Listing 27-25 shows MonthlySchedule.cs. Note that it implements IsPayDate to return true only if the argument date is the last day of the month. Listing 27-25. MonthlySchedule.cs
Listing 27-26 shows the implementation of Employee.PayDay(). This function is the generic algorithm for calculating and dispatching payment for all employees. Notice the rampant use of the STRATEGY pattern. All detailed calculations are deferred to the contained strategy classes: classification, affiliation, and method. Listing 27-26. Employee.Paysay()
Paying Hourly EmployeesGetting the hourly employees paid is a good example of the incrementalism of test-first design. I started with very trivial test cases and worked my way up to increasingly complex ones. I'll show the test cases first, and then show the production code that resulted from them. Listing 27-27 shows the simplest case. We add an hourly employee to the database and then pay that employee. Since there aren't any time cards, we expect the paycheck to have a zero value. The utility function ValidateHourlyPaycheck represents a refactoring that happened later. At first, that code was simply buried inside the test function. This test case passed after returning true from WeeklySchedule.IsPayDate(). Listing 27-27. PayrollTest.TestPaySingleHourlyEmployeeNoTimeCards()
Listing 27-28 shows two test cases. The first tests whether we can pay an employee after adding a single time card. The second tests whether we can pay overtime for a card that has more than 8 hours on it. Of course, I didn't write these two test cases at the same time. Instead, I wrote the first one and got it working, and then I wrote the second one. Listing 27-28. PayrollTest.PaySingleHourlyEmployee...()
Getting the first test case working was a matter of changing HourlyClassification. CalculatePay to loop through the time cards for the employee, add up the hours, and multiply by the pay rate. Getting the second test working forced me to change the function to calculate straight and overtime hours. The test case in Listing 27-29 makes sure that we don't pay hourly employees unless the PaydayTransaction is constructed with a Friday. Listing 27-29. PayrollTest.PaySingleHourlyEmployeeOnWrongDate()
Listing 27-30 is a test case that makes sure that we can calculate the pay for an employee who has more than one time card. Listing 27-30. PayrollTest.PaySingleHourlyEmployeeTwoTimeCards()
Finally, the test case in Listing 27-31 proves that we will pay an employee only for time cards in the current pay period. Time cards from other pay periods are ignored. Listing 27-31. PayrollTest.Test...WithTimeCardsSpanningTwoPayPeriods()
The code that makes all this work was grown incrementally, one test case at a time. The structure you see in the code that follows evolved from test case to test case. Listing 27-32 shows the appropriate fragments of HourlyClassification.cs. We simply loop through the time cards. For each time card, we check whether if it is in the pay period. If so, we calculate the pay it represents. Listing 27-32. HourlyClassification.cs (fragment)
Listing 27-33 shows that the WeeklySchedule pays only on Fridays. Listing 27-33. WeeklySchedule.IsPayDate()
Calculating the pay for commissioned employees is left as an exercise. There shouldn't be any big surprises. Pay periods: A design problemNow it's time to implement the union dues and service charges. I'm contemplating a test case that will add a salaried employee, convert it into a union member, and then pay the employee and ensure that the dues were subtracted from the pay. The coding is shown in Listing 27-34. Listing 27-34. PayrollTest.SalariedUnionMemberDues()
Note the ??? in the last two lines of the test case. What should I put there? The user stories tell me that union dues are weekly, but salaried employees are paid monthly. How many weeks are in each month? Should I simply multiply the dues by 4? That's not very accurate. I'll ask the customer what he wants.[3]
The customer tells me that union dues are accrued every Friday. So what I need to do is count the number of Fridays in the pay period and multiply by the weekly dues. There are five Fridays in November 2001, the month the test case is written for. So I can modify the test case appropriately. Counting the Fridays in a pay period implies that I need to know what the starting and ending dates of the pay period are. I have done this calculation before in the function IsInPayPeriod in Listing 27-32. (You probably wrote a similar one for the CommissionedClassification.) This function is used by the CalculatePay function of the HourlyClassification object to ensure that time cards only from the pay period are tallied. Now it seems that the UnionAffiliation object must call this function, too. But wait! What is this function doing in the HourlyClassification class? We've already determined that the association between the payment schedule and the payment classification is accidental. The function that determines the pay period ought to be in the PaymentSchedule class, not in the PaymentClassification class! It is interesting that our UML diagrams didn't help us catch this problem. The problem surfaced only when I started thinking about the test cases for UnionAffiliation. This is yet another example of how necessary coding feedback is to any design. Diagrams can be useful, but reliance on them without feedback from the code is risky business. So, how do we get the pay period out of the PaymentSchedule hierarchy and into the PaymentClassification and Affiliation hierarchies? These hierarchies do not know anything about each other. I have an idea about this. We could put the pay period dates into the Paycheck object. Right now, the Paycheck simply has the end date of the pay period. We ought to be able to get the start date in there too. Listing 27-35 shows the change made to PaydayTransaction.Execute(). Note that when the Paycheck is created, it is passed both the start and end dates of the pay period. Note also that it is the PaymentSchedule that calculates both. The changes to Paycheck should be obvious. Listing 27-35. PaydayTransaction.Execute()
The two functions in HourlyClassification and CommissionedClassification that determined whether TimeCards and SalesReceipts were within the pay period have been merged and moved into the base class PaymentClassification. See Listing 27-36. Listing 27-36. PaymentClassification.IsInPayPeriod(...)
Now we are ready to calculate the employee's union dues in UnionAffilliation. CalculateDeductions. The code in Listing 27-37 shows how this is done. The two dates that define the pay period are extracted from the paycheck and are passed to a utility function that counts the number of Fridays between them. This number is then multiplied by the weekly dues rate to calculate the dues for the pay period. Listing 27-37. UnionAffiliation.CalculateDeductions(...)
The last two test cases have to do with union service charges. The first test case, shown in Listing 27-38, makes sure that we deduct service charges appropriately. Listing 27-38. PayrollTest.HourlyUnionMemberServiceCharge()
The second test case, which posed something of a problem for me, is shown it in Listing 27-39. This test case makes sure that service charges dated outside the current pay period are not deducted. Listing 27-39. PayrollTest.ServiceChargesSpanningMultiplePayPeriods()
To implement this, I wanted UnionAffiliation::CalculateDeductions to call IsInPayPeriod. Unfortunately, we just put IsInPayPeriod in the PaymentClassification class. (See Listing 27-36.) It was convenient to put it there while it was the derivatives of PaymentClassification that needed to call it. But now other classes need it as well. So I moved the function into a DateUtil class. After all, the function is simply determining whether a given date is between two other given dates. (See Listing 27-40.) Listing 27-40. DateUtil.cs
So now, finally, we can finish the UnionAffiliation::CalculateDeductions function. I leave that as an exercise for you. Listing 27-41 shows the implementation of the Employee class. Listing 27-41. Employee.cs
|