Commit a6a5b44

mo khan <mo@mokhan.ca>
2010-07-23 18:16:55
added payroll domain model from spike project. main
1 parent 21626ea
product/client/server/domain/payroll/Annually.cs
@@ -0,0 +1,12 @@
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public class Annually : Frequency
+    {
+        public Date next(Date from_date)
+        {
+            return from_date.plus_years(1);
+        }
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/BaseDenominator.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using gorilla.commons.utility;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public class BaseDenominator : Denominator
+    {
+        readonly int denominator;
+
+        protected BaseDenominator(int denominator)
+        {
+            this.denominator = denominator;
+        }
+
+        public IEnumerable<int> each_possible_value()
+        {
+            for (var i = 0; i < denominator; i++) yield return i;
+        }
+
+        public void each(Action<int> action)
+        {
+            each_possible_value().each(x => action(x));
+        }
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Calendar.cs
@@ -1,26 +0,0 @@
-using System;
-using Gorilla.Commons.Utility;
-
-namespace presentation.windows.server.domain.payroll
-{
-    static public class Calendar
-    {
-        static Func<Date> date = () => DateTime.Now.Date;
-        static Func<Date> default_date = () => DateTime.Now.Date;
-
-        static public void stop(Func<Date> new_date)
-        {
-            date = new_date;
-        }
-
-        static public void start()
-        {
-            date = default_date;
-        }
-
-        static public Date now()
-        {
-            return date();
-        }
-    }
-}
\ No newline at end of file
product/client/server/domain/payroll/Compensation.cs
@@ -5,19 +5,13 @@ using Gorilla.Commons.Utility;
 
 namespace presentation.windows.server.domain.payroll
 {
-    public class Compensation
+    public class Compensation : Visitable<Grant>
     {
-        Money salary = Money.Zero;
         IList<Grant> grants = new List<Grant>();
 
-        public void increase_salary_to(Money newSalary)
+        public void issue_grant(Money grant_value, UnitPrice price, Fraction portion_to_issue_at_each_vest, Frequency frequency)
         {
-            salary = newSalary;
-        }
-
-        public void issue_grant(Money grant_value, UnitPrice price)
-        {
-            grants.Add(Grant.New(grant_value, price));
+            grants.Add(Grant.New(grant_value, price, portion_to_issue_at_each_vest, frequency));
         }
 
         public Grant grant_for(Date date)
@@ -25,13 +19,16 @@ namespace presentation.windows.server.domain.payroll
             return grants.Single(x => x.was_issued_on(date));
         }
 
-        public Money how_much_will_they_take_home_in(int year)
+        public Money unvested_balance(Date on_date)
         {
             var total = Money.Zero;
-            grants
-                .Where(x => x.will_vest_in(year))
-                .each(x => total = total.Plus(x.vesting_amount()));
-            return total.Plus(salary);
+            accept(new AnonymousVisitor<Grant>(grant => total = total.plus(grant.balance(on_date))));
+            return total;
+        }
+
+        public void accept(Visitor<Grant> visitor)
+        {
+            grants.each(x => visitor.visit(x));
         }
     }
 }
\ No newline at end of file
product/client/server/domain/payroll/Denominator.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public interface Denominator
+    {
+        IEnumerable<int> each_possible_value();
+        void each(Action<int> action);
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Fraction.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public interface Fraction
+    {
+        void each(Action<int> action);
+        int of(int number);
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Frequency.cs
@@ -0,0 +1,9 @@
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public interface Frequency
+    {
+        Date next(Date from_date);
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Grant.cs
@@ -1,5 +1,4 @@
 using System.Collections.Generic;
-using gorilla.commons.utility;
 using Gorilla.Commons.Utility;
 
 namespace presentation.windows.server.domain.payroll
@@ -7,48 +6,77 @@ namespace presentation.windows.server.domain.payroll
     public class Grant
     {
         Date issued_on;
-        IList<Unit> units_issued = new List<Unit>();
+        History<UnitPrice> price_history = new History<UnitPrice>();
+        Units units = Units.Empty;
+        List<Vest> expirations = new List<Vest>();
 
-        static public Grant New(Money purchase_amount, UnitPrice price)
+        static public Grant New(Money purchase_amount, UnitPrice price, Fraction portion, Frequency frequency)
         {
             var grant = new Grant
                         {
                             issued_on = Calendar.now(),
                         };
-            price.purchase_units(purchase_amount).each(x => grant.add(x));
+            grant.change_unit_price_to(price);
+            grant.purchase(purchase_amount);
+            grant.apply_vesting_frequency(portion, frequency);
             return grant;
         }
 
-        public void change_unit_price_to(UnitPrice new_price)
+        Grant() {}
+
+        public virtual void change_unit_price_to(UnitPrice new_price)
         {
-            units_issued.each(x => x.change_price(new_price));
+            price_history.record(new_price);
         }
 
-        public bool was_issued_on(Date date)
+        public virtual bool was_issued_on(Date date)
         {
             return issued_on.Equals(date);
         }
 
-        public Money current_value()
+        public virtual Money balance()
+        {
+            return balance(Calendar.now());
+        }
+
+        public virtual Money balance(Date on_date)
+        {
+            return unit_price(on_date).total_value_of(units_remaining(on_date));
+        }
+
+        Units units_remaining(Date on_date)
+        {
+            var remaining = Units.Empty;
+            foreach (var expiration in expirations)
+            {
+                remaining = remaining.combined_with(expiration.unvested_units(units, on_date));
+            }
+            return remaining;
+        }
+
+        void purchase(Money amount)
         {
-            var total = Money.Zero;
-            units_issued.each(x => total = total.Plus(x.current_value()));
-            return total;
+            units = units.combined_with(current_unit_price().purchase_units(amount));
         }
 
-        void add(Unit unit)
+        UnitPrice current_unit_price()
         {
-            units_issued.Add(unit);
+            return unit_price(Calendar.now());
         }
 
-        public bool will_vest_in(int year)
+        UnitPrice unit_price(Date on_date)
         {
-            return true;
+            return price_history.recorded(on_date);
         }
 
-        public Money vesting_amount()
+        void apply_vesting_frequency(Fraction portion, Frequency frequency)
         {
-            return current_value().divided_by(3);
+            var next_date = issued_on.minus_days(1);
+            portion.each(x =>
+            {
+                next_date = frequency.next(next_date);
+                expirations.Add(new Vest(portion, next_date));
+            });
         }
     }
 }
\ No newline at end of file
product/client/server/domain/payroll/History.cs
@@ -1,26 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using Gorilla.Commons.Utility;
 
 namespace presentation.windows.server.domain.payroll
 {
-    public class History
+    public class History<T>
     {
-        Date dateOfChange;
-        UnitPrice adjustedPrice;
+        Stack<Event<T>> events = new Stack<Event<T>>();
 
-        History() {}
+        public void record(T change)
+        {
+            events.Push(new Event<T>(change));
+        }
 
-        static public History New(UnitPrice newPrice)
+        public T recorded(Date date)
         {
-            return new History
-                   {
-                       dateOfChange = Calendar.now(),
-                       adjustedPrice = newPrice,
-                   };
+            return events.Where(x => x.occurred_on_or_before(date)).Max();
         }
 
-        public UnitPrice Adjustment()
+        class Event<K> : IComparable<Event<K>>
         {
-            return adjustedPrice;
+            public Event(K adjustment)
+            {
+                date_of_change = Calendar.now();
+                this.adjustment = adjustment;
+            }
+
+            K adjustment;
+            Date date_of_change;
+
+            public int CompareTo(Event<K> other)
+            {
+                return date_of_change.CompareTo(other.date_of_change);
+            }
+
+            public bool occurred_on_or_before(Date date)
+            {
+                return date_of_change.Equals(date) || date_of_change.is_before(date);
+            }
+
+            static public implicit operator K(Event<K> eventA)
+            {
+                return eventA.adjustment;
+            }
         }
     }
 }
\ No newline at end of file
product/client/server/domain/payroll/Money.cs
@@ -1,10 +1,11 @@
 using System;
+using gorilla.commons.utility;
 
 namespace presentation.windows.server.domain.payroll
 {
     public class Money : IEquatable<Money>
     {
-        public double value { get; private set; }
+        double value;
         static public Money Zero = new Money(0);
 
         Money(double value)
@@ -17,16 +18,21 @@ namespace presentation.windows.server.domain.payroll
             return new Money(raw);
         }
 
-        public Money Plus(Money otherMoney)
+        public virtual Money plus(Money other)
         {
-            return value + otherMoney.value;
+            return value + other.value;
         }
 
-        public bool Equals(Money other)
+        public virtual Money minus(Money other)
+        {
+            return value - other.value;
+        }
+
+        public virtual bool Equals(Money other)
         {
             if (ReferenceEquals(null, other)) return false;
             if (ReferenceEquals(this, other)) return true;
-            return other.value == value;
+            return other.value.Equals(value);
         }
 
         public override bool Equals(object obj)
@@ -42,24 +48,14 @@ namespace presentation.windows.server.domain.payroll
             return value.GetHashCode();
         }
 
-        static public bool operator ==(Money left, Money right)
-        {
-            return Equals(left, right);
-        }
-
-        static public bool operator !=(Money left, Money right)
-        {
-            return !Equals(left, right);
-        }
-
         public override string ToString()
         {
-            return value.ToString("c");
+            return "{0:C}".format(value);
         }
 
-        public Money divided_by(int denominator)
+        public Units at_price(double price)
         {
-            return new Money(value/denominator);
+            return Units.New((int)(value / price));
         }
     }
 }
\ No newline at end of file
product/client/server/domain/payroll/Monthly.cs
@@ -0,0 +1,12 @@
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public class Monthly : Frequency
+    {
+        public Date next(Date from_date)
+        {
+            return from_date.add_months(1);
+        }
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/One.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public class One<T> : Fraction where T : Denominator, new()
+    {
+        T denominator = new T();
+
+        public void each(Action<int> action)
+        {
+            denominator.each(x => action(x));
+        }
+
+        public int of(int number)
+        {
+            return number / denominator.each_possible_value().Count();
+        }
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Third.cs
@@ -0,0 +1,7 @@
+namespace presentation.windows.server.domain.payroll
+{
+    public class Third : BaseDenominator
+    {
+        public Third() : base(3) {}
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Twelfth.cs
@@ -0,0 +1,7 @@
+namespace presentation.windows.server.domain.payroll
+{
+    public class Twelfth : BaseDenominator
+    {
+        public Twelfth() : base(12) {}
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Unit.cs
@@ -1,28 +0,0 @@
-using System.Collections.Generic;
-
-namespace presentation.windows.server.domain.payroll
-{
-    public class Unit
-    {
-        UnitPrice current_price;
-        IList<History> history = new List<History>();
-
-        static public Unit New(UnitPrice price)
-        {
-            var unit = new Unit();
-            unit.change_price(price);
-            return unit;
-        }
-
-        public void change_price(UnitPrice new_price)
-        {
-            current_price = new_price ?? 0;
-            history.Add(History.New(new_price));
-        }
-
-        public Money current_value()
-        {
-            return current_price.to_money();
-        }
-    }
-}
\ No newline at end of file
product/client/server/domain/payroll/UnitPrice.cs
@@ -1,10 +1,8 @@
-using System.Collections.Generic;
-
 namespace presentation.windows.server.domain.payroll
 {
     public class UnitPrice
     {
-        double price;
+        readonly double price;
 
         UnitPrice(double price)
         {
@@ -16,19 +14,14 @@ namespace presentation.windows.server.domain.payroll
             return new UnitPrice(raw);
         }
 
-        public IEnumerable<Unit> purchase_units(Money amount)
-        {
-            for (var i = 0; i < number_of_units(amount); i++) yield return Unit.New(this);
-        }
-
-        double number_of_units(Money amount)
+        public Units purchase_units(Money amount)
         {
-            return amount.value/price;
+            return amount.at_price(price);
         }
 
-        public Money to_money()
+        public virtual Money total_value_of(Units units)
         {
-            return price;
+            return units.value_at(price);
         }
     }
 }
\ No newline at end of file
product/client/server/domain/payroll/Units.cs
@@ -0,0 +1,30 @@
+namespace presentation.windows.server.domain.payroll
+{
+    public class Units
+    {
+        static public readonly Units Empty = new Units();
+        Units() {}
+
+        static public Units New(int units)
+        {
+            return new Units {units = units};
+        }
+
+        public Money value_at(double price)
+        {
+            return price*units;
+        }
+
+        public Units combined_with(Units other)
+        {
+            return New(units + other.units);
+        }
+
+        public Units reduced_by(Fraction portion)
+        {
+            return New(portion.of(units));
+        }
+
+        int units;
+    }
+}
\ No newline at end of file
product/client/server/domain/payroll/Vest.cs
@@ -0,0 +1,26 @@
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.domain.payroll
+{
+    public class Vest
+    {
+        Fraction portion;
+        Date vesting_date;
+
+        public Vest(Fraction portion, Date vesting_date)
+        {
+            this.portion = portion;
+            this.vesting_date = vesting_date;
+        }
+
+        public Units unvested_units(Units total_units, Date date)
+        {
+            return expires_before(date) ? Units.Empty : total_units.reduced_by(portion);
+        }
+
+        bool expires_before(Date date)
+        {
+            return vesting_date.is_before(date);
+        }
+    }
+}
\ No newline at end of file
product/client/server/domain/accounting/AnonymousVisitor.cs → product/client/server/domain/AnonymousVisitor.cs
@@ -1,7 +1,7 @@
 using System;
 using gorilla.commons.utility;
 
-namespace presentation.windows.server.domain.accounting
+namespace presentation.windows.server.domain
 {
     public class AnonymousVisitor<T> : Visitor<T>
     {
product/client/server/domain/accounting/Calendar.cs → product/client/server/domain/Calendar.cs
@@ -1,7 +1,7 @@
 using System;
 using Gorilla.Commons.Utility;
 
-namespace presentation.windows.server.domain.accounting
+namespace presentation.windows.server.domain
 {
     static public class Calendar
     {
product/client/server/server.csproj
@@ -111,9 +111,9 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ConfigureApplicationDirectory.cs" />
-    <Compile Include="domain\accounting\AnonymousVisitor.cs" />
+    <Compile Include="domain\AnonymousVisitor.cs" />
     <Compile Include="domain\accounting\BOED.cs" />
-    <Compile Include="domain\accounting\Calendar.cs" />
+    <Compile Include="domain\Calendar.cs" />
     <Compile Include="domain\accounting\ConversionRatio.cs" />
     <Compile Include="domain\accounting\DateRange.cs" />
     <Compile Include="domain\accounting\Deposit.cs" />
@@ -132,6 +132,17 @@
     <Compile Include="domain\accounting\TransactionType.cs" />
     <Compile Include="domain\accounting\Withdrawal.cs" />
     <Compile Include="domain\Identifiable.cs" />
+    <Compile Include="domain\payroll\Annually.cs" />
+    <Compile Include="domain\payroll\BaseDenominator.cs" />
+    <Compile Include="domain\payroll\Denominator.cs" />
+    <Compile Include="domain\payroll\Fraction.cs" />
+    <Compile Include="domain\payroll\Frequency.cs" />
+    <Compile Include="domain\payroll\Monthly.cs" />
+    <Compile Include="domain\payroll\One.cs" />
+    <Compile Include="domain\payroll\Third.cs" />
+    <Compile Include="domain\payroll\Twelfth.cs" />
+    <Compile Include="domain\payroll\Units.cs" />
+    <Compile Include="domain\payroll\Vest.cs" />
     <Compile Include="handlers\AddNewFamilyMemberHandler.cs" />
     <Compile Include="orm\mappings\DetailAccountMapping.cs" />
     <Compile Include="ServerBootstrapper.cs" />
@@ -140,12 +151,10 @@
     <Compile Include="domain\accounting\Currency.cs" />
     <Compile Include="domain\accounting\UnitOfMeasure.cs" />
     <Compile Include="domain\Entity.cs" />
-    <Compile Include="domain\payroll\Calendar.cs" />
     <Compile Include="domain\payroll\Compensation.cs" />
     <Compile Include="domain\payroll\Grant.cs" />
     <Compile Include="domain\payroll\History.cs" />
     <Compile Include="domain\payroll\Money.cs" />
-    <Compile Include="domain\payroll\Unit.cs" />
     <Compile Include="domain\payroll\UnitPrice.cs" />
     <Compile Include="domain\Person.cs" />
     <Compile Include="handlers\FindAllFamilyHandler.cs" />
product/commons/utility/ComparableExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Gorilla.Commons.Utility
+{
+    static public class ComparableExtensions
+    {
+        static public bool is_before<T>(this T left, T right) where T : IComparable<T>
+        {
+            return left.CompareTo(right) < 0;
+        }
+
+        static public bool is_after<T>(this T left, T right) where T : IComparable<T>
+        {
+            return left.CompareTo(right) > 0;
+        }
+    }
+}
\ No newline at end of file
product/commons/utility/DateExtensions.cs
@@ -0,0 +1,25 @@
+namespace Gorilla.Commons.Utility
+{
+    static public class DateExtensions
+    {
+        static public Date plus_years(this Date date, int years)
+        {
+            return date.to_date_time().AddYears(years);
+        }
+
+        static public Date plus_days(this Date date, int days)
+        {
+            return date.to_date_time().AddDays(days);
+        }
+
+        static public Date minus_days(this Date date, int days)
+        {
+            return date.to_date_time().AddDays(-days);
+        }
+
+        static public Date add_months(this Date date, int months)
+        {
+            return date.to_date_time().AddMonths(months);
+        }
+    }
+}
\ No newline at end of file
product/commons/utility/utility.csproj
@@ -77,8 +77,10 @@
     <Compile Include="ChainedConfiguration.cs" />
     <Compile Include="ChainedMapper.cs" />
     <Compile Include="Command.cs" />
+    <Compile Include="ComparableExtensions.cs" />
     <Compile Include="Context.cs" />
     <Compile Include="ContextFactory.cs" />
+    <Compile Include="DateExtensions.cs" />
     <Compile Include="EmptyCallback.cs" />
     <Compile Include="EmptyCommand.cs" />
     <Compile Include="DefaultConstructorFactory.cs" />
product/support/unit/server/domain/accounting/SummaryAccountSpecs.cs
@@ -1,4 +1,3 @@
-using System;
 using Machine.Specifications;
 using presentation.windows.server.domain.accounting;
 
product/support/unit/server/domain/payroll/CompensationSpecs.cs
@@ -0,0 +1,55 @@
+using System;
+using Machine.Specifications;
+using presentation.windows.server.domain;
+using presentation.windows.server.domain.payroll;
+
+namespace unit.server.domain.payroll
+{
+    public class CompensationSpecs
+    {
+        public abstract class concern 
+        {
+            Establish c = () =>
+            {
+                sut = new Compensation();
+            };
+
+            static protected Compensation sut;
+        }
+
+        [Subject(typeof (Compensation))]
+        public class when_calculating_the_amount_unvested : concern
+        {
+            Because b = () =>
+            {
+                //Calendar.stop(() => new DateTime(2009, 06, 07));
+                //sut.increase_salary_to(65500);
+                Calendar.stop(() => new DateTime(2009, 09, 15));
+                sut.issue_grant(4500.00, 10.00, new One<Third>(), new Annually());
+
+                Calendar.start();
+                sut.grant_for(new DateTime(2009, 09, 15)).change_unit_price_to(20.00);
+            };
+
+            It should_indicate_that_nothing_has_vested_before_the_first_anniversary = () =>
+            {
+                sut.unvested_balance(new DateTime(2010, 09, 14)).should_be_equal_to(9000);
+            };
+
+            It should_indicate_that_one_third_has_vested_after_the_first_anniversary = () =>
+            {
+                sut.unvested_balance(new DateTime(2010, 09, 15)).should_be_equal_to(6000);
+            };
+
+            It should_indicate_that_two_thirds_has_vested_after_the_second_anniversary = () =>
+            {
+                sut.unvested_balance(new DateTime(2011, 09, 15)).should_be_equal_to(3000);
+            };
+
+            It should_indicate_that_the_complete_grant_has_vested_after_the_third_anniversary = () =>
+            {
+                sut.unvested_balance(new DateTime(2012, 09, 15)).should_be_equal_to(0);
+            };
+        }
+    }
+}
\ No newline at end of file
product/support/unit/server/domain/payroll/DateSpecs.cs
@@ -0,0 +1,25 @@
+using System;
+using Gorilla.Commons.Utility;
+using Machine.Specifications;
+
+namespace unit.server.domain.payroll
+{
+    public class DateSpecs
+    {
+        [Subject(typeof (Date))]
+        public class when_two_dates_are_the_same
+        {
+            Establish c = () =>
+            {
+                sut = new DateTime(2009, 01, 01, 01, 00, 00);
+            };
+
+            It should_be_equal = () =>
+            {
+                sut.Equals(new DateTime(2009, 01, 01, 09, 00, 01)).should_be_true();
+            };
+
+            static Date sut;
+        }
+    }
+}
\ No newline at end of file
product/support/unit/server/domain/payroll/GrantSpecs.cs
@@ -0,0 +1,60 @@
+using System;
+using Gorilla.Commons.Utility;
+using Machine.Specifications;
+using presentation.windows.server.domain;
+using presentation.windows.server.domain.payroll;
+
+namespace unit.server.domain.payroll
+{
+    public class GrantSpecs
+    {
+        public abstract class concern 
+        {
+            Establish c = () =>
+            {
+                Calendar.stop(() => new DateTime(2010, 01, 01));
+                sut = Grant.New(120, 10, new One<Twelfth>(), new Monthly());
+            };
+
+            static protected Grant sut;
+        }
+
+        [Subject(typeof (Grant))]
+        public class when_checking_what_the_outstanding_balance_of_a_grant_is : concern
+        {
+            It should_return_the_full_balance_before_the_first_vesting_date = () =>
+            {
+                Calendar.stop(() => new DateTime(2010, 01, 31));
+                sut.balance().should_be_equal_to(120);
+            };
+
+            It should_return_the_unvested_portion_after_the_first_vesting_date = () =>
+            {
+                Calendar.stop(() => new DateTime(2010, 02, 01));
+                sut.balance().should_be_equal_to(110);
+            };
+        }
+
+        [Subject(typeof (Grant))]
+        public class when_checking_what_the_value_of_a_grant_was_in_the_past : concern
+        {
+            Because b = () =>
+            {
+                Calendar.stop(() => january_15);
+                sut.change_unit_price_to(20);
+
+                Calendar.reset();
+                sut.change_unit_price_to(40);
+                result = sut.balance(january_15);
+            };
+
+            It should_return_the_correct_amount = () =>
+            {
+                result.should_be_equal_to(240);
+            };
+
+            static Money result;
+            static Date january_15 = new DateTime(2010, 01, 15);
+        }
+    }
+}
\ No newline at end of file
product/support/unit/server/domain/payroll/MoneySpecs.cs
@@ -0,0 +1,24 @@
+using Machine.Specifications;
+using presentation.windows.server.domain.payroll;
+
+namespace unit.server.domain.payroll
+{
+    public class MoneySpecs
+    {
+        [Subject(typeof (Money))]
+        public class when_two_monies_are_the_same
+        {
+            Establish c = () =>
+            {
+                sut = 100.00;
+            };
+
+            It should_be_equal = () =>
+            {
+                sut.Equals(100.00);
+            };
+
+            static Money sut;
+        }
+    }
+}
\ No newline at end of file
product/support/unit/unit.csproj
@@ -59,6 +59,10 @@
     <Compile Include="server\domain\accounting\QuantitySpecs.cs" />
     <Compile Include="server\domain\accounting\SummaryAccountSpecs.cs" />
     <Compile Include="server\domain\accounting\TransactionSpecs.cs" />
+    <Compile Include="server\domain\payroll\CompensationSpecs.cs" />
+    <Compile Include="server\domain\payroll\DateSpecs.cs" />
+    <Compile Include="server\domain\payroll\GrantSpecs.cs" />
+    <Compile Include="server\domain\payroll\MoneySpecs.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\client\client.ui\client.csproj">