This is another PayrollExample, intended to be a realistic illustration of the use of ObjectOriented programming for business applications. The code shown here is a simplified re-implementation (in Java) of C++ code used in production. It would be interesting to compare and contrast this code with the same functionality implemented using ProceduralProgramming and TableOrientedProgramming.
It is an implementation of Canadian tax deduction calculations found in the Payroll Deductions Formulas for Computer Programs document at http://www.cra-arc.gc.ca/E/pub/tg/t4127-jan/t4127-09e.pdf
The PDF spec document link appears to be invalid. Is there a new home? I did find this, which may be a later version: http://www.cra-arc.gc.ca/E/pub/tg/t4127-jan/t4127-13e.pdf
Yes, it changes every six months. The document from which this example was built was dated January 2009 and no longer exists.
For the sake of brevity, only Alberta, Ontario, and Quebec are included here.
Please note:
This is also a characteristic example of business logic and a clear justification for separation of business logic from presentation logic. (See BusinessLogicDefinition and BusinessLogicDefinitionDiscussion.) As noted above, the same payroll calculations are used in two different products. In other words, the same business logic supports two different implementations of presentation logic. If business logic and presentation logic were blended -- say, if the payroll code below prompted the user for input at some point (it doesn't) -- it would, at least, be more difficult to deploy the same code in multiple products and be more complex as well.
Commentary on this sample can be found at PayrollExampleTwoDiscussion.
Employee.java
package ca.mb.armchair.examples.payroll.taxcalc; /* * Employee information for payroll purposes. */ public class Employee { private int payPeriods; public Employee(int payPeriods) { this.payPeriods = payPeriods; } // The number of pay periods in the year public int P() {return payPeriods;} // The number of pay periods remaining in the year public int PR() {return payPeriods;} // "Total claim amount" reported on federal Form TD1. -1 means not indicated public double TC() {return -1;} // "Total claim amount" reported on the provincial or territorial TD1 form. -1 means not indicated public double TCP() {return -1;} // Annual deduction for living in a prescribed zone as indicated on Form TD1 public double HD() {return 0;} // Annual deductions such as child care expenses and support payments, etc., // authorized by a tax services office or tax centre public double F1() {return 0;} // Federal tax credits, such as medical expenses and charitable donations authorized // by a tax services office or tax centre public double K3() {return 0;} // Other provincial or territorial tax credits, such as medical expenses and charitable // donations authorized by a tax services office or tax centre public double K3P() {return 0;} // Amount for acquisition of shares in prescribed labour-sponsored venture capital // corporations public double Invest() {return 0;} // Number of disabled dependents from TD1ON public int getDisabledDependents() {return 0;} // Number of under-19 dependents from written request public int getDependentsUnder19() {return 0;} // YTD EI contributions public double EIYTD() {return 0;} // YTD CPP contributions public double CPPYTD() {return 0;} }Pay.java
package ca.mb.armchair.examples.payroll.taxcalc; /* * Payroll per-period payment information. */ public class Pay { private double I; public Pay(double I) { this.I = I; } // Gross remuneration for the pay period public double I() {return I;} // Insurable earnings for the pay period including insurable taxable benefits, bonuses, // and retroactive pay increases (Quebec) public double IE() { return I(); } // Union dues for the pay period public double U1() {return 0;} // Payroll deductions for employee contributions to a registered pension plan (RPP), // a registered retirement savings plan (RRSP), or a retirement compensation // arrangement (RCA) public double F() {return 0;} // Alimony or maintenance payments required by a legal document to be // payroll-deducted authorized by a tax services office or tax centre public double F2() {return 0;} // Additional tax deductions requested for the pay period public double L() {return 0;} }Federal.java
package ca.mb.armchair.examples.payroll.taxcalc; /** * Canada Payroll provincial and federal tax, EI & CPP/QPP deduction * calculator. * * Based on http://www.cra-arc.gc.ca/E/pub/tg/t4127-jan/t4127-09e.pdf * * Using Option 1. * * NOTE: Commissioned employees are _not_ handled, nor are bonuses, * retroactive pay increases, or other non-periodic payments. */ public abstract class Federal { protected Employee employee; protected Pay pay; public Federal(Employee employee, Pay pay) { this.employee = employee; this.pay = pay; } public static double notLessThanZero(double v) { if (v >= 0) return v; return 0; } public static double notMoreThan(double v, double maximum) { if (v > maximum) return maximum; return v; } public static double theLesserOf(double v1, double v2) { if (v1 < v2) return v1; return v2; } // Maximum EI contribution per year public double MaxEI() { return 731.79; } // EI rate public double EIRate() { return 0.0173; } // Maximum QPP or CPP contribution per year public double MaxC() { return 2118.60; } // CPP or QPP rate public double CRate() { return 0.0495; } // Employment Insurance premiums for the pay period public double EI() { return notLessThanZero(theLesserOf(MaxEI() - employee.EIYTD(), EIRate() * pay.I())); } // Canada (or Quebec) Pension Plan contributions for the pay period public double C() { return notLessThanZero(theLesserOf(MaxC() - employee.CPPYTD(), CRate() * (pay.I() - (3500 / employee.P())))); } // Annual taxable income double A() { return (employee.P() * (pay.I() - pay.F() - pay.F2() - pay.U1())) - employee.HD() - employee.F1(); } // Federal non-refundable personal tax credit double K1() { double TC = employee.TC(); if (TC == -1) TC = 10100; return 0.15 * TC; } // Estimated annual CPP contribution double PtC() { return notMoreThan(employee.P() * C(), MaxC()); } // Estimated annual Employment Insurance contribution double PtEI() { return notMoreThan(employee.P() * EI(), MaxEI()); } // Federal Canada (or Quebec) Pension Plan contributions and Employment Insurance // premium tax credits for the year double K2() { return 0.15 * PtC() + 0.15 * PtEI(); } // Canada Employment Credit double K4() { return theLesserOf(0.15 * A(), 0.15 * 1044); } // Federal tax rate applicable to the annual taxable income A double R() { if (A() <= 38832) return 0.15; else if (A() <= 77664) return 0.22; else if (A() <= 126264) return 0.26; else return 0.29; } // Federal constant double K() { if (A() <= 38832) return 0; else if (A() <= 77664) return 2718; else if (A() <= 126264) return 5825; else return 9613; } // Estimated federal and provincial or territorial tax deductions for the pay period public double T() { if (A() < 0) return pay.L(); return ((T1() + T2()) / employee.P()) + pay.L(); } // Annual federal tax deduction double T1() { return notLessThanZero(T3() - LCF()); } // Annual provincial or territorial tax deduction double T2() { return notLessThanZero(T4() + V1() + V2() - S() - LCP()); } // Annual basic federal tax double T3() { return notLessThanZero((R() * A()) - K() - K1() - K2() - employee.K3() - K4()); } // Annual basic provincial or territorial tax double T4() { return notLessThanZero((V() * A()) - KP() - K1P() - K2P() - employee.K3P() - K4P()); } // Federal labour-sponsored funds tax credit double LCF() { return theLesserOf(employee.Invest() * 0.15, 750); } // Provincial or territorial Canada Pension Plan contribution and Employment Insurance // premiums tax credits for the year double K2P() { return PtC() * V() + PtEI() * V(); } // Provincial or territorial labour-sponsored funds tax credit double LCP() {return 0;} // Provincial or territorial tax rate for the year double V() {return 0;} // Surtax calculated on the basic provincial or territorial tax double V1() {return 0;} // Additional tax calculated on taxable income (applies to Ontario Health Premium only) double V2() {return 0;} // Ontario or British Columbia provincial tax reduction double S() {return 0;} // Provincial or territorial constant double KP() {return 0;} // Provincial or territorial non-refundable personal tax credit double K1P() {return 0;} // Provincial or territorial Canada Employment Credit (applies to Yukon only) double K4P() {return 0;} }Alberta.java
package ca.mb.armchair.examples.payroll.taxcalc; /* * Alberta provincial & federal tax deduction calculator. */ public class Alberta extends Federal { public Alberta(Employee e, Pay p) { super(e, p); } // Provincial tax rate double V() { return 0.10; } // Provincial non-refundable personal tax credit double K1P() { double TCP = employee.TCP(); if (TCP == -1) TCP = 16775; return 0.10 * TCP; } }Quebec.java
package ca.mb.armchair.examples.payroll.taxcalc; /* * Quebec provincial alterations to federal tax deduction calculator. */ public class Quebec extends Federal { public Quebec(Employee e, Pay p) { super(e, p); } // EI rate public double EIRate() { return 0.0138; } // EI yearly maximum public double MaxEI() { return 583.74; } // Federal Canada/Quebec Pension Plan & Employment Insurance premium // tax credits for the year. double K2() { double IE = notMoreThan(employee.P() * pay.IE() * 0.00484, 300.08); return 0.15 * PtC() + 0.15 * PtEI() + 0.15 * IE; } // Annual federal tax deduction. double T1() { return notLessThanZero(T3() - LCF()) - notLessThanZero(0.165 * T3()); } // Provincial tax calculations obtained separately from Quebec. Not provided here. double T2() { return 0; } }Ontario.java
package ca.mb.armchair.examples.payroll.taxcalc; /* * Ontario provincial & federal tax deduction calculator. */ public class Ontario extends Federal { public Ontario(Employee e, Pay p) { super(e, p); } // Provincial non-refundable personal tax credit double K1P() { double TCP = employee.TCP(); if (TCP == -1) TCP = 8881; return 0.0605 * TCP; } // Provincial Canada Pension Plan contribution and Employment Insurance // premiums tax credits for the year double K2P() { return PtC() * 0.0605 + PtEI() * 0.0605; } // Provincial tax rate double V() { if (A() <= 36848) return 0.0605; else if (A() <= 73698) return 0.0915; else return 0.1116; } // Provincial constant double KP() { if (A() <= 36848) return 0; else if (A() <= 73698) return 1142; else return 2624; } // Surtax calculated on basic provincial tax double V1() { if (T4() <= 4257) return 0; else if (T4() <= 5370) return 0.20 * (T4() - 4257); else return 0.20 * (T4() - 4257) + 0.36 * (T4() - 5370); } // Additional tax on taxable income (Ontario Health Premium) double V2() { if (A() <= 20000) return 0; else if (A() <= 36000) return theLesserOf(300, 0.06 * (A() - 20000)); else if (A() <= 48000) return theLesserOf(450, 300 + 0.06 * (A() - 36000)); else if (A() <= 72000) return theLesserOf(600, 450 + 0.25 * (A() - 48000)); else if (A() <= 200000) return theLesserOf(750, 600 + 0.25 * (A() - 72000)); else return theLesserOf(900, 750 + 0.25 * (A() - 200000)); } // Ontario provincial tax reduction for dependents. double S() { double Y = employee.getDisabledDependents() * 379 + employee.getDependentsUnder19() * 379; return notLessThanZero(theLesserOf(T4() + V1(), 2 * (205 + Y) - (T4() + V1()))); } // Provincial labour-sponsored-funds tax credit double LCP() { return theLesserOf(1125, 0.15 * employee.Invest()); } }
Tests for the above, using JUnit 4. Test values were obtained from examples in the Canada Payroll document.
TestTax.java
package ca.mb.armchair.examples.payroll.tests; import static org.junit.Assert.assertEquals; import org.junit.Test; import ca.mb.armchair.examples.payroll.taxcalc.*; public class TestTax { @Test public void testABTaxP12() { Employee julie = new Employee(12); Pay juliePay = new Pay(1800); Federal calculator = new Alberta(julie, juliePay); assertEquals(144.46, calculator.T()); } @Test public void testABTaxP26() { Employee julie = new Employee(26); Pay juliePay = new Pay(2300); Federal calculator = new Alberta(julie, juliePay); assertEquals(475.24, calculator.T()); } @Test public void testABTaxP52() { Employee julie = new Employee(52); Pay juliePay = new Pay(2500); Federal calculator = new Alberta(julie, juliePay); assertEquals(712.03, calculator.T()); } @Test public void testABTaxP52_2() { Employee julie = new Employee(52) { public double TC() { return 26467; } public double TCP() { return 33550; } public double Invest() { return 2000; } }; Pay juliePay = new Pay(1100) { public double F() { return 50; } public double U1() { return 20; } }; Federal calculator = new Alberta(julie, juliePay); assertEquals(113.98, calculator.T()); } @Test public void testQCTaxP12() { Employee julie = new Employee(12); Pay juliePay = new Pay(1800); Federal calculator = new Quebec(julie, juliePay); assertEquals(95.58, calculator.T()); } @Test public void testQCTaxP26() { Employee julie = new Employee(26); Pay juliePay = new Pay(2300); Federal calculator = new Quebec(julie, juliePay); assertEquals(267.12, calculator.T()); } @Test public void testQCTaxP52() { Employee julie = new Employee(52); Pay juliePay = new Pay(2500); Federal calculator = new Quebec(julie, juliePay); assertEquals(416.94, calculator.T()); } @Test public void testOTTaxP12() { Employee julie = new Employee(12); Pay juliePay = new Pay(1800); Federal calculator = new Ontario(julie, juliePay); assertEquals(180.55, calculator.T()); } @Test public void testOTTaxP26() { Employee julie = new Employee(26); Pay juliePay = new Pay(2300); Federal calculator = new Ontario(julie, juliePay); assertEquals(483.03, calculator.T()); } @Test public void testOTTaxP52() { Employee julie = new Employee(52); Pay juliePay = new Pay(2500); Federal calculator = new Ontario(julie, juliePay); assertEquals(795.87, calculator.T()); } @Test public void testOTTaxP52_2() { Employee julie = new Employee(52) { public double TC() { return 26467; } public double TCP() { return 16422; } public double Invest() { return 2000; } public int getDisabledDependents() { return 1; } public int getDependentsUnder19() { return 2; } }; Pay juliePay = new Pay(1100) { public double F() { return 50; } public double U1() { return 20; } }; Federal calculator = new Ontario(julie, juliePay); assertEquals(134.86, calculator.T()); } }
See PayrollExample, JavaLanguage, PayrollExampleTwoDiscussion