Commit 4b3170a
Changed files (69)
product
desktop.ui
service
domain
accounting
hierarchy
payroll
property_bag
specs
unit
service
domain
product/desktop.ui/solidware.financials.csproj
@@ -60,8 +60,12 @@
<Reference Include="Autofac">
<HintPath>..\..\thirdparty\autofac\Autofac.dll</HintPath>
</Reference>
- <Reference Include="Castle.Core, Version=2.5.1.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL" />
- <Reference Include="Db4objects.Db4o, Version=8.0.184.15484, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL" />
+ <Reference Include="Castle.Core">
+ <HintPath>..\..\thirdparty\castle\Castle.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="Db4objects.Db4o, Version=8.0.184.15484, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL">
+ <HintPath>..\..\thirdparty\db4o\Db4objects.Db4o.dll</HintPath>
+ </Reference>
<Reference Include="gorilla.infrastructure">
<HintPath>..\..\thirdparty\commons\gorilla.infrastructure.dll</HintPath>
</Reference>
product/service/domain/accounting/Account.cs
@@ -0,0 +1,9 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public interface Account
+ {
+ Quantity balance();
+ Quantity balance(Date date);
+ Quantity balance(Range<Date> period);
+ }
+}
\ No newline at end of file
product/service/domain/accounting/BOED.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class BOED : SimpleUnitOfMeasure
+ {
+ public override string pretty_print(double amount)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/ConversionRatio.cs
@@ -0,0 +1,18 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public class ConversionRatio
+ {
+ double rate;
+ static public readonly ConversionRatio Default = new ConversionRatio(1);
+
+ public ConversionRatio(double rate)
+ {
+ this.rate = rate;
+ }
+
+ public double applied_to(double amount)
+ {
+ return amount*rate;
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Currency.cs
@@ -0,0 +1,57 @@
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class Currency : SimpleUnitOfMeasure
+ {
+ static public readonly Currency USD = new Currency("USD");
+ static public readonly Currency CAD = new Currency("CAD");
+
+ Currency(string pneumonic)
+ {
+ this.pneumonic = pneumonic;
+ }
+
+ public override string pretty_print(double amount)
+ {
+ return "{0:C} {1}".format(amount, this);
+ }
+
+ public bool Equals(Currency other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Equals(other.pneumonic, pneumonic);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Currency)) return false;
+ return Equals((Currency) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return (pneumonic != null ? pneumonic.GetHashCode() : 0);
+ }
+
+ static public bool operator ==(Currency left, Currency right)
+ {
+ return Equals(left, right);
+ }
+
+ static public bool operator !=(Currency left, Currency right)
+ {
+ return !Equals(left, right);
+ }
+
+ public override string ToString()
+ {
+ return pneumonic;
+ }
+
+ string pneumonic;
+ }
+}
\ No newline at end of file
product/service/domain/accounting/DateRange.cs
@@ -0,0 +1,15 @@
+namespace solidware.financials.service.domain.accounting
+{
+ static public class DateRange
+ {
+ static public Range<Date> up_to(Date date)
+ {
+ return Date.First.to(date);
+ }
+
+ static public Range<Date> to(this Date start, Date end)
+ {
+ return new SequentialRange<Date>(start, end);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Deposit.cs
@@ -0,0 +1,10 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public class Deposit : TransactionType
+ {
+ public Quantity adjust(Quantity balance, Quantity adjustment)
+ {
+ return balance.plus(adjustment);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/DetailAccount.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using gorilla.utility;
+using solidware.financials.service.domain.payroll;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class DetailAccount : Account, Visitable<Entry>
+ {
+ DetailAccount(UnitOfMeasure unit_of_measure)
+ {
+ this.unit_of_measure = unit_of_measure;
+ }
+
+ static public DetailAccount New(UnitOfMeasure unit_of_measure)
+ {
+ return new DetailAccount(unit_of_measure);
+ }
+
+ public void deposit(double amount)
+ {
+ deposit(amount, unit_of_measure);
+ }
+
+ public void deposit(double amount, UnitOfMeasure units)
+ {
+ add(Entry.New<Deposit>(amount, units));
+ }
+
+ public void withdraw(double amount, UnitOfMeasure units)
+ {
+ add(Entry.New<Withdrawal>(amount, units));
+ }
+
+ public void add(Entry new_entry)
+ {
+ entries.Add(new_entry);
+ }
+
+ public Quantity balance()
+ {
+ return balance(Calendar.now());
+ }
+
+ public Quantity balance(Date date)
+ {
+ return balance(DateRange.up_to(date));
+ }
+
+ public Quantity balance(Range<Date> period)
+ {
+ var result = new Quantity(0, unit_of_measure);
+ accept(new AnonymousVisitor<Entry>(x =>
+ {
+ if (x.booked_in(period)) result = x.adjust(result);
+ }));
+ return result;
+ }
+
+ public void accept(Visitor<Entry> visitor)
+ {
+ foreach (var entry in entries) visitor.visit(entry);
+ }
+
+ IList<Entry> entries = new List<Entry>();
+ UnitOfMeasure unit_of_measure;
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Entry.cs
@@ -0,0 +1,34 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public class Entry
+ {
+ static public Entry New<Transaction>(double amount, UnitOfMeasure units) where Transaction : TransactionType, new()
+ {
+ return New<Transaction>(new Quantity(amount, units));
+ }
+
+ static public Entry New<Transaction>(Quantity amount) where Transaction : TransactionType, new()
+ {
+ return new Entry
+ {
+ when_booked = Calendar.now(),
+ transaction_type = new Transaction(),
+ amount = amount,
+ };
+ }
+
+ public virtual Quantity adjust(Quantity balance)
+ {
+ return transaction_type.adjust(balance, amount);
+ }
+
+ public virtual bool booked_in(Range<Date> period)
+ {
+ return period.includes(when_booked);
+ }
+
+ Date when_booked;
+ TransactionType transaction_type;
+ Quantity amount;
+ }
+}
\ No newline at end of file
product/service/domain/accounting/MCF.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class MCF : SimpleUnitOfMeasure
+ {
+ public override string pretty_print(double amount)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Potential.cs
@@ -0,0 +1,27 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public class Potential<Transaction> : PotentialEntry where Transaction : TransactionType, new()
+ {
+ static public Potential<Transaction> New(DetailAccount destination, Quantity amount)
+ {
+ return new Potential<Transaction>
+ {
+ account = destination,
+ amount = amount,
+ };
+ }
+
+ public Quantity combined_with(Quantity other)
+ {
+ return new Transaction().adjust(other, amount);
+ }
+
+ public void commit()
+ {
+ account.add(Entry.New<Transaction>(amount));
+ }
+
+ DetailAccount account;
+ Quantity amount;
+ }
+}
\ No newline at end of file
product/service/domain/accounting/PotentialEntry.cs
@@ -0,0 +1,8 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public interface PotentialEntry
+ {
+ Quantity combined_with(Quantity other);
+ void commit();
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Quantity.cs
@@ -0,0 +1,64 @@
+using System;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class Quantity : IEquatable<Quantity>
+ {
+ double amount;
+ UnitOfMeasure units;
+
+ public Quantity(double amount, UnitOfMeasure units)
+ {
+ this.units = units;
+ this.amount = amount;
+ }
+
+ public Quantity plus(Quantity other)
+ {
+ return new Quantity(amount + other.convert_to(units).amount, units);
+ }
+
+ public Quantity subtract(Quantity other)
+ {
+ return new Quantity(amount - other.convert_to(units).amount, units);
+ }
+
+ public Quantity convert_to(UnitOfMeasure unit_of_measure)
+ {
+ return new Quantity(unit_of_measure.convert(amount, units), unit_of_measure);
+ }
+
+ static public implicit operator double(Quantity quanity)
+ {
+ return quanity.amount;
+ }
+
+ public bool Equals(Quantity other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.amount.Equals(amount) && Equals(other.units, units);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Quantity)) return false;
+ return Equals((Quantity) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (amount.GetHashCode()*397) ^ (units != null ? units.GetHashCode() : 0);
+ }
+ }
+
+ public override string ToString()
+ {
+ return units.pretty_print(amount);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Range.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public interface Range<T> where T : IComparable<T>
+ {
+ bool includes(T item);
+ }
+}
\ No newline at end of file
product/service/domain/accounting/SequentialRange.cs
@@ -0,0 +1,27 @@
+using System;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class SequentialRange<T> : Range<T> where T : IComparable<T>
+ {
+ readonly T start;
+ readonly T end;
+
+ public SequentialRange(T start, T end)
+ {
+ this.start = start;
+ this.end = end;
+ }
+
+ public bool includes(T item)
+ {
+ return item.CompareTo(start) >= 0 && item.CompareTo(end) <= 0;
+ }
+
+ public override string ToString()
+ {
+ return "{0} to {1}".format(start, end);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/SimpleUnitOfMeasure.cs
@@ -0,0 +1,21 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public delegate ConversionRatio RateTable(UnitOfMeasure unitCurrency, UnitOfMeasure referenceCurrency);
+
+ public abstract class SimpleUnitOfMeasure : UnitOfMeasure
+ {
+ public double convert(double amount, UnitOfMeasure other)
+ {
+ return rate_table(this, other).applied_to(amount);
+ }
+
+ public abstract string pretty_print(double amount);
+
+ static RateTable rate_table = (x, y) => ConversionRatio.Default;
+
+ static public void provide_rate(RateTable current_rates)
+ {
+ rate_table = current_rates;
+ }
+ }
+}
\ No newline at end of file
product/service/domain/accounting/SummaryAccount.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class SummaryAccount : Account
+ {
+ static public SummaryAccount New(UnitOfMeasure unit_of_measure)
+ {
+ return new SummaryAccount
+ {
+ unit_of_measure = unit_of_measure
+ };
+ }
+
+ public void add(Account account)
+ {
+ accounts.Add(account);
+ }
+
+ public Quantity balance()
+ {
+ return balance(Calendar.now());
+ }
+
+ public Quantity balance(Date date)
+ {
+ return balance(DateRange.up_to(date));
+ }
+
+ public Quantity balance(Range<Date> period)
+ {
+ var result = new Quantity(0, unit_of_measure);
+ accounts.each(x => result = result.plus(x.balance(period)));
+ return result;
+ }
+
+ ICollection<Account> accounts = new HashSet<Account>();
+ UnitOfMeasure unit_of_measure;
+ }
+}
\ No newline at end of file
product/service/domain/accounting/Transaction.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class Transaction
+ {
+ static public Transaction New(UnitOfMeasure reference_units)
+ {
+ return new Transaction(reference_units);
+ }
+
+ Transaction(UnitOfMeasure reference)
+ {
+ reference_units = reference;
+ }
+
+ public void deposit(DetailAccount destination, Quantity amount)
+ {
+ deposits.Add(Potential<Deposit>.New(destination, amount));
+ }
+
+ public void withdraw(DetailAccount source, Quantity amount)
+ {
+ withdrawals.Add(Potential<Withdrawal>.New(source, amount));
+ }
+
+ public void post()
+ {
+ ensure_zero_balance();
+ deposits.Union(withdrawals).each(x => x.commit());
+ }
+
+ void ensure_zero_balance()
+ {
+ var balance = calculate_total(deposits.Union(withdrawals));
+ if (balance == 0) return;
+
+ throw new TransactionDoesNotBalance();
+ }
+
+ Quantity calculate_total(IEnumerable<PotentialEntry> potential_transactions)
+ {
+ var result = new Quantity(0, reference_units);
+ potential_transactions.each(x => result = x.combined_with(result));
+ return result;
+ }
+
+ List<PotentialEntry> deposits = new List<PotentialEntry>();
+ List<PotentialEntry> withdrawals = new List<PotentialEntry>();
+ UnitOfMeasure reference_units;
+ }
+}
\ No newline at end of file
product/service/domain/accounting/TransactionDoesNotBalance.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace solidware.financials.service.domain.accounting
+{
+ public class TransactionDoesNotBalance : Exception
+ {
+
+ }
+}
\ No newline at end of file
product/service/domain/accounting/TransactionType.cs
@@ -0,0 +1,7 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public interface TransactionType
+ {
+ Quantity adjust(Quantity balance, Quantity adjustment);
+ }
+}
\ No newline at end of file
product/service/domain/accounting/UnitOfMeasure.cs
@@ -0,0 +1,9 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public interface UnitOfMeasure
+ {
+ double convert(double amount, UnitOfMeasure other);
+ string pretty_print(double amount);
+ }
+
+}
\ No newline at end of file
product/service/domain/accounting/Withdrawal.cs
@@ -0,0 +1,10 @@
+namespace solidware.financials.service.domain.accounting
+{
+ public class Withdrawal : TransactionType
+ {
+ public Quantity adjust(Quantity balance, Quantity adjustment)
+ {
+ return balance.subtract(adjustment);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/hierarchy/Hierarchy.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.hierarchy
+{
+ public class Hierarchy : Visitable<Hierarchy>
+ {
+ static readonly Hierarchy Null = new NullHierarchy();
+
+ public void accept(Visitor<Hierarchy> visitor)
+ {
+ visitor.visit(this);
+ children.each(x => x.accept(visitor));
+ }
+
+ public void add(Hierarchy child)
+ {
+ child.parent = this;
+ children.Add(child);
+ }
+
+ public void move_to(Hierarchy new_tree)
+ {
+ parent.remove(this);
+ new_tree.add(this);
+ }
+
+ public virtual void remove(Hierarchy descendant)
+ {
+ if (children.Contains(descendant))
+ children.Remove(descendant);
+ else
+ {
+ var hierarchy = children.SingleOrDefault(x => x.contains(descendant));
+ if(null != hierarchy) hierarchy.remove(descendant);
+ }
+ }
+
+ public bool contains(Hierarchy child)
+ {
+ return everyone().Any(x => x.Equals(child));
+ }
+
+ IEnumerable<Hierarchy> everyone()
+ {
+ var all = new List<Hierarchy> {this};
+ foreach (var child in children) all.AddRange(child.everyone());
+ return all;
+ }
+
+ public bool belongs_to(Hierarchy tree)
+ {
+ return tree.contains(this);
+ }
+
+ IList<Hierarchy> children = new List<Hierarchy>();
+ Hierarchy parent = Null;
+
+ class NullHierarchy : Hierarchy
+ {
+ public override void remove(Hierarchy descendant) {}
+ }
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Annually.cs
@@ -0,0 +1,10 @@
+namespace solidware.financials.service.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/service/domain/payroll/AnonymousVisitor.cs
@@ -0,0 +1,20 @@
+using System;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public class AnonymousVisitor<T> : Visitor<T>
+ {
+ Action<T> action;
+
+ public AnonymousVisitor(Action<T> action)
+ {
+ this.action = action;
+ }
+
+ public void visit(T item)
+ {
+ action(item);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/payroll/BaseDenominator.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using gorilla.utility;
+
+namespace solidware.financials.service.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/service/domain/payroll/Compensation.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public class Compensation : Visitable<Grant>
+ {
+ IList<Grant> grants = new List<Grant>();
+
+ public void issue_grant(Money grant_value, UnitPrice price, Fraction portion_to_issue_at_each_vest, Frequency frequency)
+ {
+ grants.Add(Grant.New(grant_value, price, portion_to_issue_at_each_vest, frequency));
+ }
+
+ public Grant grant_for(Date date)
+ {
+ return grants.Single(x => x.was_issued_on(date));
+ }
+
+ public Money unvested_balance(Date on_date)
+ {
+ var total = Money.Zero;
+ 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/service/domain/payroll/Denominator.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public interface Denominator
+ {
+ IEnumerable<int> each_possible_value();
+ void each(Action<int> action);
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Fraction.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public interface Fraction
+ {
+ void each(Action<int> action);
+ int of(int number);
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Frequency.cs
@@ -0,0 +1,7 @@
+namespace solidware.financials.service.domain.payroll
+{
+ public interface Frequency
+ {
+ Date next(Date from_date);
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Grant.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public class Grant
+ {
+ Date issued_on;
+ 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, Fraction portion, Frequency frequency)
+ {
+ var grant = new Grant
+ {
+ issued_on = Calendar.now(),
+ };
+ grant.change_unit_price_to(price);
+ grant.purchase(purchase_amount);
+ grant.apply_vesting_frequency(portion, frequency);
+ return grant;
+ }
+
+ Grant() {}
+
+ public virtual void change_unit_price_to(UnitPrice new_price)
+ {
+ price_history.record(new_price);
+ }
+
+ public virtual bool was_issued_on(Date date)
+ {
+ return issued_on.Equals(date);
+ }
+
+ 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)
+ {
+ units = units.combined_with(current_unit_price().purchase_units(amount));
+ }
+
+ UnitPrice current_unit_price()
+ {
+ return unit_price(Calendar.now());
+ }
+
+ UnitPrice unit_price(Date on_date)
+ {
+ return price_history.recorded(on_date);
+ }
+
+ void apply_vesting_frequency(Fraction portion, Frequency frequency)
+ {
+ 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/service/domain/payroll/History.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public class History<T>
+ {
+ Stack<Event<T>> events = new Stack<Event<T>>();
+
+ public void record(T change)
+ {
+ events.Push(new Event<T>(change));
+ }
+
+ public T recorded(Date date)
+ {
+ return events.Where(x => x.occurred_on_or_before(date)).Max();
+ }
+
+ class Event<K> : IComparable<Event<K>>
+ {
+ 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/service/domain/payroll/Money.cs
@@ -0,0 +1,61 @@
+using System;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain.payroll
+{
+ public class Money : IEquatable<Money>
+ {
+ double value;
+ static public Money Zero = new Money(0);
+
+ Money(double value)
+ {
+ this.value = value;
+ }
+
+ static public implicit operator Money(double raw)
+ {
+ return new Money(raw);
+ }
+
+ public virtual Money plus(Money other)
+ {
+ return value + other.value;
+ }
+
+ 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.Equals(value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Money)) return false;
+ return Equals((Money) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return value.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return "{0:C}".format(value);
+ }
+
+ public Units at_price(double price)
+ {
+ return Units.New((int)(value / price));
+ }
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Monthly.cs
@@ -0,0 +1,10 @@
+namespace solidware.financials.service.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/service/domain/payroll/One.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq;
+
+namespace solidware.financials.service.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/service/domain/payroll/Third.cs
@@ -0,0 +1,7 @@
+namespace solidware.financials.service.domain.payroll
+{
+ public class Third : BaseDenominator
+ {
+ public Third() : base(3) {}
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Twelfth.cs
@@ -0,0 +1,7 @@
+namespace solidware.financials.service.domain.payroll
+{
+ public class Twelfth : BaseDenominator
+ {
+ public Twelfth() : base(12) {}
+ }
+}
\ No newline at end of file
product/service/domain/payroll/UnitPrice.cs
@@ -0,0 +1,27 @@
+namespace solidware.financials.service.domain.payroll
+{
+ public class UnitPrice
+ {
+ readonly double price;
+
+ UnitPrice(double price)
+ {
+ this.price = price;
+ }
+
+ static public implicit operator UnitPrice(double raw)
+ {
+ return new UnitPrice(raw);
+ }
+
+ public Units purchase_units(Money amount)
+ {
+ return amount.at_price(price);
+ }
+
+ public virtual Money total_value_of(Units units)
+ {
+ return units.value_at(price);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/payroll/Units.cs
@@ -0,0 +1,30 @@
+namespace solidware.financials.service.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/service/domain/payroll/Vest.cs
@@ -0,0 +1,24 @@
+namespace solidware.financials.service.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/service/domain/property_bag/Bag.cs
@@ -0,0 +1,10 @@
+namespace solidware.financials.service.domain.property_bag
+{
+ public class Bag
+ {
+ static public PropertyBag For<Target>()
+ {
+ return new PropertyBagWithoutTarget<Target>();
+ }
+ }
+}
\ No newline at end of file
product/service/domain/property_bag/Property.cs
@@ -0,0 +1,7 @@
+namespace solidware.financials.service.domain.property_bag
+{
+ public interface Property
+ {
+ bool represents(string name);
+ }
+}
\ No newline at end of file
product/service/domain/property_bag/PropertyBag.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace solidware.financials.service.domain.property_bag
+{
+ public interface PropertyBag
+ {
+ Property property_named(string name);
+ IEnumerable<Property> all();
+ }
+}
\ No newline at end of file
product/service/domain/property_bag/PropertyBagWithoutTarget.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace solidware.financials.service.domain.property_bag
+{
+ public class PropertyBagWithoutTarget<T> : PropertyBag
+ {
+ IEnumerable<Property> all_properties;
+
+ public PropertyBagWithoutTarget()
+ {
+ all_properties = typeof (T).GetProperties().Select(x => (Property) new PropertyReference<T>(x));
+ }
+
+ public Property property_named(string name)
+ {
+ if (all_properties.Any(x => x.represents(name)))
+ all_properties.Single(x => x.represents(name));
+ return new UnknownProperty();
+ }
+
+ public IEnumerable<Property> all()
+ {
+ return all_properties;
+ }
+ }
+}
\ No newline at end of file
product/service/domain/property_bag/PropertyReference.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+
+namespace solidware.financials.service.domain.property_bag
+{
+ public class PropertyReference<Target> : Property
+ {
+ readonly PropertyInfo property;
+
+ public PropertyReference(PropertyInfo property)
+ {
+ this.property = property;
+ }
+
+ public bool represents(string name)
+ {
+ return string.Compare(property.Name, name, true) == 0;
+ }
+ }
+}
\ No newline at end of file
product/service/domain/property_bag/UnknownProperty.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace solidware.financials.service.domain.property_bag
+{
+ public class UnknownProperty : Property
+ {
+ public bool represents(string name)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
product/service/domain/Calendar.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace solidware.financials.service.domain
+{
+ 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();
+ }
+
+ static public void reset()
+ {
+ date = default_date;
+ }
+ }
+}
\ No newline at end of file
product/service/domain/ComparableExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace solidware.financials.service.domain
+{
+ 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/service/domain/Date.cs
@@ -0,0 +1,57 @@
+using System;
+using gorilla.utility;
+
+namespace solidware.financials.service.domain
+{
+ public class Date : IComparable<Date>
+ {
+ long ticks;
+ static public readonly Date First = new Date(DateTime.MinValue);
+ static public readonly Date Last = new Date(DateTime.MaxValue);
+
+ Date(DateTime date)
+ {
+ ticks = date.Date.Ticks;
+ }
+
+ static public implicit operator Date(DateTime raw)
+ {
+ return new Date(raw);
+ }
+
+ public bool Equals(Date other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.ticks == ticks;
+ }
+
+ public int CompareTo(Date other)
+ {
+ return to_date_time().CompareTo(new DateTime(other.ticks));
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Date)) return false;
+ return Equals((Date) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return ticks.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return "{0}".format(to_date_time());
+ }
+
+ public DateTime to_date_time()
+ {
+ return new DateTime(ticks);
+ }
+ }
+}
\ No newline at end of file
product/service/domain/DateExtensions.cs
@@ -0,0 +1,25 @@
+namespace solidware.financials.service.domain
+{
+ 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/service/domain/Person.cs
@@ -1,5 +1,3 @@
-using gorilla.utility;
-
namespace solidware.financials.service.domain
{
public class Person : Entity
product/service/service.csproj
@@ -31,7 +31,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
- <Reference Include="Castle.Core, Version=2.5.1.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL" />
+ <Reference Include="Castle.Core">
+ <HintPath>..\..\thirdparty\castle\Castle.Core.dll</HintPath>
+ </Reference>
<Reference Include="Db4objects.Db4o">
<HintPath>..\..\thirdparty\db4o\Db4objects.Db4o.dll</HintPath>
</Reference>
@@ -52,6 +54,55 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DB4OBootstrapper.cs" />
+ <Compile Include="domain\accounting\Account.cs" />
+ <Compile Include="domain\accounting\BOED.cs" />
+ <Compile Include="domain\accounting\ConversionRatio.cs" />
+ <Compile Include="domain\accounting\Currency.cs" />
+ <Compile Include="domain\accounting\DateRange.cs" />
+ <Compile Include="domain\accounting\Deposit.cs" />
+ <Compile Include="domain\accounting\DetailAccount.cs" />
+ <Compile Include="domain\accounting\Entry.cs" />
+ <Compile Include="domain\accounting\MCF.cs" />
+ <Compile Include="domain\accounting\Potential.cs" />
+ <Compile Include="domain\accounting\PotentialEntry.cs" />
+ <Compile Include="domain\accounting\Quantity.cs" />
+ <Compile Include="domain\accounting\Range.cs" />
+ <Compile Include="domain\accounting\SequentialRange.cs" />
+ <Compile Include="domain\accounting\SimpleUnitOfMeasure.cs" />
+ <Compile Include="domain\accounting\SummaryAccount.cs" />
+ <Compile Include="domain\accounting\Transaction.cs" />
+ <Compile Include="domain\accounting\TransactionDoesNotBalance.cs" />
+ <Compile Include="domain\accounting\TransactionType.cs" />
+ <Compile Include="domain\accounting\UnitOfMeasure.cs" />
+ <Compile Include="domain\accounting\Withdrawal.cs" />
+ <Compile Include="domain\Calendar.cs" />
+ <Compile Include="domain\ComparableExtensions.cs" />
+ <Compile Include="domain\Date.cs" />
+ <Compile Include="domain\DateExtensions.cs" />
+ <Compile Include="domain\hierarchy\Hierarchy.cs" />
+ <Compile Include="domain\payroll\Annually.cs" />
+ <Compile Include="domain\payroll\AnonymousVisitor.cs" />
+ <Compile Include="domain\payroll\BaseDenominator.cs" />
+ <Compile Include="domain\payroll\Compensation.cs" />
+ <Compile Include="domain\payroll\Denominator.cs" />
+ <Compile Include="domain\payroll\Fraction.cs" />
+ <Compile Include="domain\payroll\Frequency.cs" />
+ <Compile Include="domain\payroll\Grant.cs" />
+ <Compile Include="domain\payroll\History.cs" />
+ <Compile Include="domain\payroll\Money.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\UnitPrice.cs" />
+ <Compile Include="domain\payroll\Units.cs" />
+ <Compile Include="domain\payroll\Vest.cs" />
+ <Compile Include="domain\property_bag\Bag.cs" />
+ <Compile Include="domain\property_bag\Property.cs" />
+ <Compile Include="domain\property_bag\PropertyBag.cs" />
+ <Compile Include="domain\property_bag\PropertyBagWithoutTarget.cs" />
+ <Compile Include="domain\property_bag\PropertyReference.cs" />
+ <Compile Include="domain\property_bag\UnknownProperty.cs" />
<Compile Include="handlers\AddIncomeCommandMessageHandler.cs" />
<Compile Include="handlers\AddNewFamilyMemberHandler.cs" />
<Compile Include="domain\Entity.cs" />
product/specs/unit/service/domain/accounting/BOEDSpecs.cs
@@ -0,0 +1,47 @@
+using Machine.Specifications;
+using solidware.financials.service.domain.accounting;
+
+namespace specs.unit.service.domain.accounting
+{
+ public class BOEDSpecs
+ {
+ public abstract class concern
+ {
+ Establish context = () =>
+ {
+ sut = new BOED();
+ };
+
+ Cleanup clean = () =>
+ {
+ SimpleUnitOfMeasure.provide_rate((x, y) => ConversionRatio.Default);
+ };
+
+ static protected BOED sut;
+ }
+
+ [Concern(typeof (BOED))]
+ public class when_converting_one_barrel_of_oil_equivalent_to_one_mcf : concern
+ {
+ Establish c = () =>
+ {
+ SimpleUnitOfMeasure.provide_rate((x, y) =>
+ {
+ return new ConversionRatio(6);
+ });
+ };
+
+ Because b = () =>
+ {
+ result = sut.convert(1, new MCF());
+ };
+
+ It should_return_the_corrent_value = () =>
+ {
+ result.should_be_equal_to(6);
+ };
+
+ static double result;
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/accounting/CurrencySpecs.cs
@@ -0,0 +1,53 @@
+using Machine.Specifications;
+using solidware.financials.service.domain.accounting;
+
+namespace specs.unit.service.domain.accounting
+{
+ public class CurrencySpecs
+ {
+ public abstract class concern : runner<Currency>
+ {
+ Cleanup clean = () => {
+ SimpleUnitOfMeasure.provide_rate((x, y) => ConversionRatio.Default);
+ };
+ }
+
+ [Concern(typeof (Currency))]
+ public class when_converting_one_USD_dollar_to_CAD : concern
+ {
+ Establish c = () =>
+ {
+ SimpleUnitOfMeasure.provide_rate((x, y) => new ConversionRatio(1.05690034));
+ };
+
+ It should_return_the_correct_amount = () =>
+ {
+ sut.convert(1, Currency.CAD).should_be_equal_to(1.05690034);
+ };
+
+ protected override Currency create_sut()
+ {
+ return Currency.USD;
+ }
+ }
+
+ [Concern(typeof (Currency))]
+ public class when_converting_one_CAD_dollar_to_USD : concern
+ {
+ Establish c = () =>
+ {
+ SimpleUnitOfMeasure.provide_rate((x, y) => new ConversionRatio(0.95057));
+ };
+
+ It should_return_the_correct_amount = () =>
+ {
+ sut.convert(1.05690034d, Currency.USD).should_be_equal_to(1.0046577561938002d);
+ };
+
+ protected override Currency create_sut()
+ {
+ return Currency.CAD;
+ }
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/accounting/DetailAccountSpecs.cs
@@ -0,0 +1,45 @@
+using Machine.Specifications;
+using solidware.financials.service.domain.accounting;
+
+namespace specs.unit.service.domain.accounting
+{
+ public class DetailAccountSpecs
+ {
+ public abstract class concern : runner<DetailAccount>
+ {
+ protected override DetailAccount create_sut()
+ {
+ return DetailAccount.New(Currency.CAD);
+ }
+ }
+
+ [Concern(typeof (DetailAccount))]
+ public class when_depositing_money_in_to_an_account : concern
+ {
+ Because b = () =>
+ {
+ sut.deposit(100.01, Currency.CAD);
+ };
+
+ It should_adjust_the_balance = () =>
+ {
+ sut.balance().should_be_equal_to(new Quantity(100.01, Currency.CAD));
+ };
+ }
+
+ [Concern(typeof (DetailAccount))]
+ public class when_withdrawing_money_from_an_account : concern
+ {
+ Because b = () =>
+ {
+ sut.deposit(100.01);
+ sut.withdraw(10.00, Currency.CAD);
+ };
+
+ It should_adjust_the_balance = () =>
+ {
+ sut.balance().should_be_equal_to(new Quantity(90.01, Currency.CAD));
+ };
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/accounting/QuantitySpecs.cs
@@ -0,0 +1,37 @@
+using Machine.Specifications;
+using solidware.financials.service.domain.accounting;
+
+namespace specs.unit.service.domain.accounting
+{
+ public class QuantitySpecs
+ {
+ public abstract class concern
+ {
+ Establish blah = () =>
+ {
+ sut = new Quantity(100, Currency.CAD);
+ };
+
+ static protected Quantity sut;
+ }
+
+ [Concern(typeof (Quantity))]
+ public class when_checking_if_two_quantities_are_equal : concern
+ {
+ It should_return_true_when_they_represent_the_same_amount_and_units = () =>
+ {
+ sut.should_be_equal_to(new Quantity(100, Currency.CAD));
+ };
+
+ It should_return_false_when_they_are_not_the_same_amount = () =>
+ {
+ sut.ShouldNotEqual(new Quantity(1, Currency.CAD));
+ };
+
+ It should_return_false_when_they_represent_different_currencies = () =>
+ {
+ sut.ShouldNotEqual(new Quantity(100, Currency.USD));
+ };
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/accounting/SummaryAccountSpecs.cs
@@ -0,0 +1,45 @@
+using Machine.Specifications;
+using solidware.financials.service.domain.accounting;
+
+namespace specs.unit.service.domain.accounting
+{
+ public class SummaryAccountSpecs
+ {
+ public abstract class concern : runner<SummaryAccount>
+ {
+ protected override SummaryAccount create_sut()
+ {
+ return SummaryAccount.New(Currency.CAD);
+ }
+ }
+
+ [Concern(typeof (SummaryAccount))]
+ public class when_retrieving_the_balance_from_a_summary_account : concern
+ {
+ Establish c = () =>
+ {
+ cash = DetailAccount.New(Currency.CAD);
+ credit = DetailAccount.New(Currency.CAD);
+
+ cash.deposit(50, Currency.CAD);
+ credit.deposit(50, Currency.CAD);
+ };
+
+ Because b = () =>
+ {
+ sut.add(cash);
+ sut.add(credit);
+ result = sut.balance();
+ };
+
+ It should_sum_the_balance_for_each_detail_account = () =>
+ {
+ result.should_be_equal_to(new Quantity(100.00, Currency.CAD));
+ };
+
+ static Quantity result;
+ static DetailAccount cash;
+ static DetailAccount credit;
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/accounting/TransactionSpecs.cs
@@ -0,0 +1,117 @@
+using System;
+using Machine.Specifications;
+using solidware.financials.service.domain.accounting;
+
+namespace specs.unit.service.domain.accounting
+{
+ public class TransactionSpecs
+ {
+ public class concern : runner<Transaction>
+ {
+ protected override Transaction create_sut()
+ {
+ return Transaction.New(Currency.CAD);
+ }
+ Establish c = () => {
+ //sut = Activator.CreateInstance(GetType());
+ sut = new concern().create_sut();
+ };
+ }
+
+ [Concern(typeof (Transaction))]
+ public class when_transferring_funds_from_one_account_to_another : concern
+ {
+ Establish c = () =>
+ {
+ source_account = DetailAccount.New(Currency.CAD);
+ destination_account = DetailAccount.New(Currency.CAD);
+ source_account.add(Entry.New<Deposit>(100, Currency.CAD));
+ };
+
+ Because b = () =>
+ {
+ sut.deposit(destination_account, new Quantity(100, Currency.CAD));
+ sut.withdraw(source_account, new Quantity(100, Currency.CAD));
+ sut.post();
+ };
+
+ It should_increase_the_balance_of_the_destination_account = () =>
+ {
+ destination_account.balance().should_be_equal_to(new Quantity(100, Currency.CAD));
+ };
+
+ It should_decrease_the_balance_of_the_source_account = () =>
+ {
+ source_account.balance().should_be_equal_to(new Quantity(0, Currency.CAD));
+ };
+
+ static DetailAccount source_account;
+ static DetailAccount destination_account;
+ }
+
+ [Concern(typeof (Transaction))]
+ public class when_transferring_funds_from_one_account_to_multiple_accounts : concern
+ {
+ Establish c = () =>
+ {
+ chequing = DetailAccount.New(Currency.CAD);
+ savings = DetailAccount.New(Currency.CAD);
+ rrsp = DetailAccount.New(Currency.CAD);
+ chequing.add(Entry.New<Deposit>(100, Currency.CAD));
+ };
+
+ Because b = () =>
+ {
+ sut.withdraw(chequing, new Quantity(100, Currency.CAD));
+ sut.deposit(savings, new Quantity(75, Currency.CAD));
+ sut.deposit(rrsp, new Quantity(25, Currency.CAD));
+ sut.post();
+ };
+
+ It should_increase_the_balance_of_each_destination_account = () =>
+ {
+ savings.balance().should_be_equal_to(new Quantity(75, Currency.CAD));
+ rrsp.balance().should_be_equal_to(new Quantity(25, Currency.CAD));
+ };
+
+ It should_decrease_the_balance_of_the_source_account = () =>
+ {
+ chequing.balance().should_be_equal_to(new Quantity(0, Currency.CAD));
+ };
+
+ static DetailAccount chequing;
+ static DetailAccount savings;
+ static DetailAccount rrsp;
+ }
+
+ [Concern(typeof (Transaction))]
+ public class when_a_transaction_does_not_reconcile_to_a_zero_balance : concern
+ {
+ Establish c = () =>
+ {
+ chequing = DetailAccount.New(Currency.CAD);
+ savings = DetailAccount.New(Currency.CAD);
+ chequing.add(Entry.New<Deposit>(100, Currency.CAD));
+ };
+
+ Because b = () =>
+ {
+ sut.withdraw(chequing, new Quantity(1, Currency.CAD));
+ sut.deposit(savings, new Quantity(100, Currency.CAD));
+ call = () =>
+ {
+ sut.post();
+ };
+ };
+
+ It should_not_transfer_any_money = () =>
+ {
+ call.should_have_thrown<TransactionDoesNotBalance>();
+ };
+
+ static DetailAccount chequing;
+ static DetailAccount savings;
+ static Action call;
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/hierarchy/HierarchySpecs.cs
@@ -0,0 +1,126 @@
+using gorilla.utility;
+using Machine.Specifications;
+using solidware.financials.service.domain.hierarchy;
+
+namespace specs.unit.service.domain.hierarchy
+{
+ public class HierarchySpecs
+ {
+ public abstract class concern : runner<Hierarchy>
+ {
+ protected override Hierarchy create_sut()
+ {
+ return new Hierarchy();
+ }
+ }
+
+ [Concern(typeof (Hierarchy))]
+ public class when_visiting_item_in_a_hierarchy : concern
+ {
+ Establish c = () =>
+ {
+ visitor = Create.an<Visitor<Hierarchy>>();
+ middle = new Hierarchy();
+ bottom = new Hierarchy();
+ };
+
+ Because b = () =>
+ {
+ top = sut;
+ top.add(middle);
+ middle.add(bottom);
+ top.accept(visitor);
+ };
+
+ It should_visit_everyone = () =>
+ {
+ visitor.received(x => x.visit(top));
+ visitor.received(x => x.visit(top));
+ visitor.received(x => x.visit(middle));
+ visitor.received(x => x.visit(bottom));
+ };
+
+ static Visitor<Hierarchy> visitor;
+ static Hierarchy top;
+ static Hierarchy middle;
+ static Hierarchy bottom;
+ }
+
+ [Concern(typeof (Hierarchy))]
+ public class when_moving_a_sub_tree_from_one_tree_to_another : concern
+ {
+ Establish c = () =>
+ {
+ old_tree = new Hierarchy();
+ new_tree = new Hierarchy();
+ child = new Hierarchy();
+ };
+
+ Because b = () =>
+ {
+ old_tree.add(child);
+ child.move_to(new_tree);
+ };
+
+ It should_remove_the_sub_tree_from_the_old_tree = () =>
+ {
+ old_tree.contains(child).should_be_false();
+ child.belongs_to(old_tree).should_be_false();
+ };
+
+ It should_add_the_sub_tree_to_the_new_one = () =>
+ {
+ new_tree.contains(child).should_be_true();
+ child.belongs_to(new_tree).should_be_true();
+ };
+
+ static Hierarchy old_tree;
+ static Hierarchy new_tree;
+ static Hierarchy child;
+ }
+
+ [Concern(typeof (Hierarchy))]
+ public class when_removing_a_descendant_from_a_tree_that_it_does_not_belong_to : concern
+ {
+ Establish c = () =>
+ {
+ orphan = new Hierarchy();
+ };
+
+ Because b = () =>
+ {
+ sut.remove(orphan);
+ };
+
+ It should_ignore_the_descdendant = () =>
+ {
+ sut.contains(orphan).should_be_false();
+ orphan.belongs_to(sut).should_be_false();
+ };
+
+ static Hierarchy orphan;
+ }
+
+ [Concern(typeof (Hierarchy))]
+ public class when_moving_a_child_to_another_tree_but_does_not_already_belong_to_a_tree : concern
+ {
+ Establish c = () =>
+ {
+ orphan = new Hierarchy();
+ };
+
+ Because b = () =>
+ {
+ orphan.move_to(sut);
+ };
+
+ It should_move_the_orphan_to_the_new_family = () =>
+ {
+ sut.contains(orphan).should_be_true();
+ orphan.belongs_to(sut).should_be_true();
+ };
+
+ static Hierarchy orphan;
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/payroll/CompensationSpecs.cs
@@ -0,0 +1,53 @@
+using System;
+using Machine.Specifications;
+using solidware.financials.service.domain;
+using solidware.financials.service.domain.payroll;
+
+namespace specs.unit.service.domain.payroll
+{
+ public class CompensationSpecs
+ {
+ public abstract class concern : runner<Compensation>
+ {
+ protected override Compensation create_sut()
+ {
+ return new Compensation();
+ }
+ }
+
+ [Concern(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/specs/unit/service/domain/payroll/DateSpecs.cs
@@ -0,0 +1,25 @@
+using System;
+using Machine.Specifications;
+using solidware.financials.service.domain;
+
+namespace specs.unit.service.domain.payroll
+{
+ public class DateSpecs
+ {
+ public abstract class concern : runner<Date> {}
+
+ [Concern(typeof (Date))]
+ public class when_two_dates_are_the_same : concern
+ {
+ It should_be_equal = () =>
+ {
+ sut.Equals(new DateTime(2009, 01, 01, 09, 00, 01)).should_be_true();
+ };
+
+ protected override Date create_sut()
+ {
+ return new DateTime(2009, 01, 01, 01, 00, 00);
+ }
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/payroll/GrantSpecs.cs
@@ -0,0 +1,57 @@
+using System;
+using Machine.Specifications;
+using solidware.financials.service.domain;
+using solidware.financials.service.domain.payroll;
+
+namespace specs.unit.service.domain.payroll
+{
+ public class GrantSpecs
+ {
+ public abstract class concern : runner<Grant>
+ {
+ protected override Grant create_sut()
+ {
+ Calendar.stop(() => new DateTime(2010, 01, 01));
+ return Grant.New(120, 10, new One<Twelfth>(), new Monthly());
+ }
+ }
+
+ [Concern(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);
+ };
+ }
+
+ [Concern(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/specs/unit/service/domain/payroll/MoneySpecs.cs
@@ -0,0 +1,24 @@
+using Machine.Specifications;
+using solidware.financials.service.domain.payroll;
+
+namespace specs.unit.service.domain.payroll
+{
+ public class MoneySpecs
+ {
+ public abstract class concern : runner<Money> {}
+
+ [Concern(typeof (Money))]
+ public class when_two_monies_are_the_same : concern
+ {
+ It should_be_equal = () =>
+ {
+ sut.Equals(100.00);
+ };
+
+ protected override Money create_sut()
+ {
+ return 100.00;
+ }
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/property_bag/PropertyBagSpecs.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Machine.Specifications;
+using solidware.financials.service.domain.property_bag;
+
+namespace specs.unit.service.domain.property_bag
+{
+ public class PropertyBagSpecs
+ {
+ public abstract class concern : runner<PropertyBag>
+ {
+ Establish c = () => Console.Out.WriteLine("init concern");
+
+ protected override PropertyBag create_sut()
+ {
+ return Bag.For<TargetType>();
+ }
+ }
+
+ [Concern(typeof (PropertyBag))]
+ public class when_creating_a_property_bag_from_a_known_type : concern
+ {
+
+ It should_include_each_property_from_the_target_type = () =>
+ {
+ sut.property_named("name").should_not_be_null();
+ };
+
+ Establish c = () => { Console.Out.WriteLine("blah");};
+
+ It should_not_contain_properties_that_are_not_on_the_target_type = () =>
+ {
+ sut.property_named("blah").should_be_an_instance_of<UnknownProperty>();
+ };
+ }
+
+ [Concern(typeof (PropertyBag))]
+ public class when_iterating_through_each_property_in_the_bag : concern
+ {
+ Establish c = () => Console.Out.WriteLine("init when_iterating_through_each_property_in_the_bag");
+
+ It should_contain_a_reference_for_each_property_on_the_target_type = () =>
+ {
+ results.Count().should_be_equal_to(1);
+ };
+
+ Because b = () =>
+ {
+ results = sut.all();
+ };
+
+ static IEnumerable<Property> results;
+ }
+ }
+
+ public class TargetType
+ {
+ public string name { get; set; }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/DateSpecs.cs
@@ -0,0 +1,57 @@
+using System;
+using Machine.Specifications;
+using solidware.financials.service.domain;
+
+namespace specs.unit.service.domain
+{
+ public class DateSpecs
+ {
+ public abstract class concern {}
+
+ [Concern(typeof (Date))]
+ public class when_checking_if_a_date_is_before_another : concern
+ {
+ Establish c = () =>
+ {
+ today = DateTime.Now;
+ tomorrow = today.plus_days(1);
+ };
+
+ It should_return_true_when_it_is = () =>
+ {
+ today.is_before(tomorrow).should_be_true();
+ };
+
+ It should_return_false_when_it_is_not = () =>
+ {
+ tomorrow.is_before(today).should_be_false();
+ };
+
+ static Date today;
+ static Date tomorrow;
+ }
+
+ [Concern(typeof (Date))]
+ public class when_checking_if_a_date_is_after_another : concern
+ {
+ Establish c = () =>
+ {
+ today = DateTime.Now;
+ tomorrow = today.plus_days(1);
+ };
+
+ It should_return_true_when_it_is = () =>
+ {
+ tomorrow.is_after(today).should_be_true();
+ };
+
+ It should_return_false_when_it_is_not = () =>
+ {
+ today.is_after(tomorrow).should_be_false();
+ };
+
+ static Date today;
+ static Date tomorrow;
+ }
+ }
+}
\ No newline at end of file
product/specs/unit/service/domain/runner.cs
@@ -0,0 +1,14 @@
+using Machine.Specifications;
+
+namespace specs.unit.service.domain
+{
+ public class runner<T>
+ {
+ static protected T sut;
+
+ protected virtual T create_sut()
+ {
+ return default(T);
+ }
+ }
+}
\ No newline at end of file
product/specs/Assertions.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Machine.Specifications;
+
+namespace specs
+{
+ static public class Assertions
+ {
+ static public void should_be_equal_to<T>(this T item_to_validate, T expected_value)
+ {
+ item_to_validate.ShouldEqual(expected_value);
+ }
+
+ static public void should_be_the_same_instance_as<T>(this T left, T right)
+ {
+ ReferenceEquals(left, right).ShouldBeTrue();
+ }
+
+ static public void should_be_null<T>(this T item)
+ {
+ item.ShouldBeNull();
+ }
+
+ static public void should_not_be_null<T>(this T item) where T : class
+ {
+ item.ShouldNotBeNull();
+ }
+
+ static public void should_be_greater_than<T>(this T actual, T expected) where T : IComparable
+ {
+ actual.ShouldBeGreaterThan(expected);
+ }
+
+ static public void should_be_less_than(this int actual, int expected)
+ {
+ actual.ShouldBeLessThan(expected);
+ }
+
+ static public void should_contain<T>(this IEnumerable<T> items_to_peek_in_to, T items_to_look_for)
+ {
+ items_to_peek_in_to.Contains(items_to_look_for).should_be_true();
+ }
+
+ static public void should_not_contain<T>(this IEnumerable<T> items_to_peek_into, T item_to_look_for)
+ {
+ items_to_peek_into.Contains(item_to_look_for).should_be_false();
+ }
+
+ static public void should_be_an_instance_of<T>(this object item)
+ {
+ item.should_be_an_instance_of(typeof (T));
+ }
+
+ static public void should_be_an_instance_of(this object item, Type type)
+ {
+ item.ShouldBe(type);
+ }
+
+ static public void should_have_thrown<TheException>(this Action action) where TheException : Exception
+ {
+ typeof (TheException).ShouldBeThrownBy(action);
+ }
+
+ static public void should_be_true(this bool item)
+ {
+ item.ShouldBeTrue();
+ }
+
+ static public void should_be_false(this bool item)
+ {
+ item.ShouldBeFalse();
+ }
+
+ static public void should_contain<T>(this IEnumerable<T> items, params T[] items_to_find)
+ {
+ foreach (var item_to_find in items_to_find)
+ {
+ items.should_contain(item_to_find);
+ }
+ }
+
+ static public void should_only_contain<T>(this IEnumerable<T> items, params T[] itemsToFind)
+ {
+ items.Count().should_be_equal_to(itemsToFind.Length);
+ items.should_contain(itemsToFind);
+ }
+
+ static public void should_be_equal_ignoring_case(this string item, string other)
+ {
+ item.ShouldBeEqualIgnoringCase(other);
+ }
+ }
+}
\ No newline at end of file
product/specs/ConcernAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+using Machine.Specifications;
+
+namespace specs
+{
+ public class ConcernAttribute : SubjectAttribute
+ {
+ public ConcernAttribute(Type subjectType) : base(subjectType) {}
+ public ConcernAttribute(Type subjectType, string subject) : base(subjectType, subject) {}
+ public ConcernAttribute(string subject) : base(subject) {}
+ }
+}
\ No newline at end of file
product/specs/specs.csproj
@@ -36,7 +36,9 @@
<Reference Include="Castle.Core">
<HintPath>..\..\thirdparty\castle\Castle.Core.dll</HintPath>
</Reference>
- <Reference Include="Db4objects.Db4o, Version=8.0.184.15484, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL" />
+ <Reference Include="Db4objects.Db4o">
+ <HintPath>..\..\thirdparty\db4o\Db4objects.Db4o.dll</HintPath>
+ </Reference>
<Reference Include="gorilla.infrastructure, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL" />
<Reference Include="gorilla.utility, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -59,10 +61,26 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Assertions.cs" />
+ <Compile Include="ConcernAttribute.cs" />
<Compile Include="Mocking.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Create.cs" />
<Compile Include="unit\infrastructure\ProxyFactorySpecs.cs" />
+ <Compile Include="unit\service\domain\accounting\BOEDSpecs.cs" />
+ <Compile Include="unit\service\domain\accounting\CurrencySpecs.cs" />
+ <Compile Include="unit\service\domain\accounting\DetailAccountSpecs.cs" />
+ <Compile Include="unit\service\domain\accounting\QuantitySpecs.cs" />
+ <Compile Include="unit\service\domain\runner.cs" />
+ <Compile Include="unit\service\domain\accounting\SummaryAccountSpecs.cs" />
+ <Compile Include="unit\service\domain\accounting\TransactionSpecs.cs" />
+ <Compile Include="unit\service\domain\DateSpecs.cs" />
+ <Compile Include="unit\service\domain\hierarchy\HierarchySpecs.cs" />
+ <Compile Include="unit\service\domain\payroll\CompensationSpecs.cs" />
+ <Compile Include="unit\service\domain\payroll\DateSpecs.cs" />
+ <Compile Include="unit\service\domain\payroll\GrantSpecs.cs" />
+ <Compile Include="unit\service\domain\payroll\MoneySpecs.cs" />
+ <Compile Include="unit\service\domain\property_bag\PropertyBagSpecs.cs" />
<Compile Include="unit\service\handlers\AddIncomeCommandMessageHandlerSpecs.cs" />
<Compile Include="unit\service\orm\DB4OUnitOfWorkFactorySpecs.cs" />
<Compile Include="unit\service\orm\DB4OUnitOfWorkSpecs.cs" />