Commit a6a5b44
Changed files (30)
product
client
commons
support
unit
server
domain
accounting
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">