Commit db19ef3
Changed files (54)
product
client
client.ui
bootstrappers
presenters
views
common
server
domain
accounting
handlers
orm
mappings
commons
infrastructure
container
logging
support
unit
product/client/client.ui/bootstrappers/Bootstrapper.cs → product/client/client.ui/bootstrappers/ClientBootstrapper.cs
@@ -18,7 +18,7 @@ using Rhino.Queues;
namespace presentation.windows.bootstrappers
{
- static public class Bootstrapper
+ static public class ClientBootstrapper
{
static public ShellWindow create_window()
{
@@ -31,10 +31,7 @@ namespace presentation.windows.bootstrappers
builder.Register(x => shell_window).SingletonScoped();
builder.Register(x => shell_window).As<RegionManager>().SingletonScoped();
- //needs startups
- builder.Register<StartServiceBus>().As<NeedStartup>();
- builder.Register<ComposeShell>().As<NeedStartup>();
- builder.Register<ConfigureMappings>().As<NeedStartup>();
+ register_needs_startup(builder);
// infrastructure
builder.Register<Log4NetLogFactory>().As<LogFactory>().SingletonScoped();
@@ -46,15 +43,42 @@ namespace presentation.windows.bootstrappers
builder.Register(x => new RhinoPublisher("server", 2200, manager)).As<ServiceBus>().SingletonScoped();
builder.Register(x => new RhinoReceiver(manager.GetQueue("client"), x.Resolve<CommandProcessor>())).As<RhinoReceiver>().As<Receiver>().SingletonScoped();
- // presentation infrastructure
+ register_presentation_infrastructure(builder);
+ register_presenters(builder);
+ register_for_message_to_listen_for(builder);
+
+ //shell_window.Closed += (o, e) => Resolve.the<ServiceBus>().publish<ApplicationShuttingDown>();
+ shell_window.Closed += (o, e) => Resolve.the<CommandProcessor>().stop();
+ shell_window.Closed += (o, e) => manager.Dispose();
+ shell_window.Closed += (o, e) => Resolve.the<IEnumerable<NeedsShutdown>>();
+
+ Resolve.the<IEnumerable<NeedStartup>>().each(x => x.run());
+ Resolve.the<CommandProcessor>().run();
+ return shell_window;
+ }
+
+ static void register_needs_startup(ContainerBuilder builder)
+ {
+ builder.Register<StartServiceBus>().As<NeedStartup>();
+ builder.Register<ComposeShell>().As<NeedStartup>();
+ builder.Register<ConfigureMappings>().As<NeedStartup>();
+ }
+
+ static void register_presentation_infrastructure(ContainerBuilder builder)
+ {
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
builder.Register<WpfApplicationController>().As<ApplicationController>().SingletonScoped();
builder.Register<WpfPresenterFactory>().As<PresenterFactory>().SingletonScoped();
builder.Register<SynchronizedEventAggregator>().As<EventAggregator>().SingletonScoped();
//builder.Register(x => AsyncOperationManager.SynchronizationContext);
builder.Register(x => SynchronizationContext.Current);
+ builder.Register<AsynchronousCommandProcessor>().As<CommandProcessor>().SingletonScoped();
+ //builder.Register<SynchronousCommandProcessor>().As<CommandProcessor>().SingletonScoped();
+ builder.Register<WPFCommandBuilder>().As<UICommandBuilder>();
+ }
- // presenters
+ static void register_presenters(ContainerBuilder builder)
+ {
builder.Register<StatusBarPresenter>().SingletonScoped();
builder.Register<SelectedFamilyMemberPresenter>().SingletonScoped();
builder.Register<AddFamilyMemberPresenter>();
@@ -64,22 +88,12 @@ namespace presentation.windows.bootstrappers
builder.Register<CancelCommand>();
builder.Register<AddNewDetailAccountPresenter>();
builder.Register<AddNewDetailAccountPresenter.CreateNewAccount>();
+ }
- // commanding
- builder.Register<AsynchronousCommandProcessor>().As<CommandProcessor>().SingletonScoped();
- //builder.Register<SynchronousCommandProcessor>().As<CommandProcessor>().SingletonScoped();
- builder.Register<WPFCommandBuilder>().As<UICommandBuilder>();
-
- // queries
+ static void register_for_message_to_listen_for(ContainerBuilder builder)
+ {
builder.Register<PublishEventHandler<AddedNewFamilyMember>>().As<Handler>();
-
- Resolve.the<IEnumerable<NeedStartup>>().each(x => x.run());
- Resolve.the<CommandProcessor>().run();
-
- shell_window.Closed += (o, e) => Resolve.the<ServiceBus>().publish<ApplicationShuttingDown>();
- shell_window.Closed += (o, e) => Resolve.the<CommandProcessor>().stop();
- shell_window.Closed += (o, e) => manager.Dispose();
- return shell_window;
+ builder.Register<PublishEventHandler<NewAccountCreated>>().As<Handler>();
}
}
}
\ No newline at end of file
product/client/client.ui/presenters/AddNewDetailAccountPresenter.cs
@@ -38,7 +38,7 @@ namespace presentation.windows.presenters
protected override void run(AddNewDetailAccountPresenter presenter)
{
- bus.publish<common.messages.CreateNewDetailAccount>(x =>
+ bus.publish<common.messages.CreateNewDetailAccountCommand>(x =>
{
x.account_name = presenter.account_name;
x.currency = presenter.currency;
product/client/client.ui/presenters/PersonDetails.cs
@@ -13,7 +13,7 @@ namespace presentation.windows.presenters
public override string ToString()
{
- return "{0} {1}".formatted_using(first_name, last_name);
+ return "{0} {1}".format(first_name, last_name);
}
}
}
\ No newline at end of file
product/client/client.ui/views/ShellWIndow.xaml.cs
@@ -32,7 +32,7 @@ namespace presentation.windows.views
void ensure_that_the_region_exists<Region>()
{
if (!regions.ContainsKey(typeof (Region)))
- throw new Exception("Could not find region: {0}".formatted_using(typeof (Region)));
+ throw new Exception("Could not find region: {0}".format(typeof (Region)));
}
}
}
\ No newline at end of file
product/client/client.ui/client.csproj
@@ -150,7 +150,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationController.cs" />
- <Compile Include="bootstrappers\Bootstrapper.cs" />
+ <Compile Include="bootstrappers\ClientBootstrapper.cs" />
<Compile Include="bootstrappers\ConfigureMappings.cs" />
<Compile Include="bootstrappers\StartServiceBus.cs" />
<Compile Include="events\SelectedFamilyMember.cs" />
product/client/client.ui/Program.cs
@@ -34,7 +34,7 @@ namespace presentation.windows
new WPFApplication
{
ShutdownMode = ShutdownMode.OnMainWindowClose,
- }.Run(Bootstrapper.create_window());
+ }.Run(ClientBootstrapper.create_window());
}
static string get_service_startup_path(string[] args)
product/client/common/messages/CreateNewDetailAccount.cs → product/client/common/messages/CreateNewDetailAccountCommand.cs
@@ -5,7 +5,7 @@ namespace presentation.windows.common.messages
{
[Serializable]
[ProtoContract]
- public class CreateNewDetailAccount
+ public class CreateNewDetailAccountCommand
{
[ProtoMember(1)]
public string account_name { get; set; }
product/client/common/messages/NewAccountCreated.cs
@@ -5,7 +5,7 @@ namespace presentation.windows.common.messages
{
[Serializable]
[ProtoContract]
- public class NewAccountCreated
+ public class NewAccountCreated : IEvent
{
[ProtoMember(1)]
public string name { get; set; }
product/client/common/common.csproj
@@ -88,7 +88,7 @@
<Compile Include="MessageHandler.cs" />
<Compile Include="messages\AddedNewFamilyMember.cs" />
<Compile Include="messages\ApplicationShuttingDown.cs" />
- <Compile Include="messages\CreateNewDetailAccount.cs" />
+ <Compile Include="messages\CreateNewDetailAccountCommand.cs" />
<Compile Include="messages\FamilyMemberToAdd.cs" />
<Compile Include="messages\FindAllFamily.cs" />
<Compile Include="messages\NewAccountCreated.cs" />
product/client/common/NeedStartup.cs
@@ -3,4 +3,6 @@ using gorilla.commons.utility;
namespace presentation.windows.common
{
public interface NeedStartup : Command {}
+
+ public interface NeedsShutdown : Command {}
}
\ No newline at end of file
product/client/common/RhinoPublisher.cs
@@ -30,7 +30,7 @@ namespace presentation.windows.common
{
using (var transaction = new TransactionScope(TransactionScopeOption.RequiresNew))
{
- var destination = "rhino.queues://localhost:{0}/{1}".formatted_using(port, destination_queue);
+ var destination = "rhino.queues://localhost:{0}/{1}".format(port, destination_queue);
this.log().debug("sending {0} to {1}", item, destination);
sender.Send(new Uri(destination), create_payload_from(item));
transaction.Complete();
product/client/server/domain/accounting/Account.cs
@@ -1,12 +1,11 @@
-using System;
+using Gorilla.Commons.Utility;
namespace presentation.windows.server.domain.accounting
{
- public class Account
+ public interface Account
{
- static public Account New(string account_name, UnitOfMeasure units)
- {
- throw new NotImplementedException();
- }
+ Quantity balance();
+ Quantity balance(Date date);
+ Quantity balance(Range<Date> period);
}
}
\ No newline at end of file
product/client/server/domain/accounting/AnonymousVisitor.cs
@@ -0,0 +1,20 @@
+using System;
+using gorilla.commons.utility;
+
+namespace presentation.windows.server.domain.accounting
+{
+ public class AnonymousVisitor<T> : Visitor<T>
+ {
+ readonly Action<T> action;
+
+ public AnonymousVisitor(Action<T> action)
+ {
+ this.action = action;
+ }
+
+ public void visit(T item_to_visit)
+ {
+ action(item_to_visit);
+ }
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/BOED.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace presentation.windows.server.domain.accounting
+{
+ public class BOED : SimpleUnitOfMeasure
+ {
+ public override string pretty_print(double amount)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/Calendar.cs
@@ -0,0 +1,31 @@
+using System;
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.domain.accounting
+{
+ 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/client/server/domain/accounting/ConversionRatio.cs
@@ -0,0 +1,18 @@
+namespace presentation.windows.server.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/client/server/domain/accounting/Currency.cs
@@ -1,12 +1,66 @@
-using System;
+using System.Collections.Generic;
+using gorilla.commons.utility;
+using System.Linq;
namespace presentation.windows.server.domain.accounting
{
- public class Currency : UnitOfMeasure
+ public class Currency : SimpleUnitOfMeasure
{
- static public Currency named(string currency)
+ static public readonly List<Currency> all = new List<Currency>();
+ static public readonly Currency USD = new Currency("USD");
+ static public readonly Currency CAD = new Currency("CAD");
+
+ Currency(string pneumonic)
+ {
+ this.pneumonic = pneumonic;
+ all.add(this);
+ }
+
+ 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;
+
+ static public UnitOfMeasure named(string currency)
{
- throw new NotImplementedException();
+ return all.Single(x => x.pneumonic.Equals(currency));
}
}
}
\ No newline at end of file
product/client/server/domain/accounting/DateRange.cs
@@ -0,0 +1,17 @@
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.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/client/server/domain/accounting/Deposit.cs
@@ -0,0 +1,10 @@
+namespace presentation.windows.server.domain.accounting
+{
+ public class Deposit : TransactionType
+ {
+ public Quantity adjust(Quantity balance, Quantity adjustment)
+ {
+ return balance.plus(adjustment);
+ }
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/DetailAccount.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using gorilla.commons.utility;
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.domain.accounting
+{
+ public class DetailAccount : Entity, Account, Visitable<Entry>
+ {
+ protected DetailAccount() {}
+
+ 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 virtual void deposit(double amount)
+ {
+ deposit(amount, unit_of_measure);
+ }
+
+ public virtual void deposit(double amount, UnitOfMeasure units)
+ {
+ add(Entry.New<Deposit>(amount, units));
+ }
+
+ public virtual void withdraw(double amount, UnitOfMeasure units)
+ {
+ add(Entry.New<Withdrawal>(amount, units));
+ }
+
+ public virtual void add(Entry new_entry)
+ {
+ entries.Add(new_entry);
+ }
+
+ public virtual Quantity balance()
+ {
+ return balance(Calendar.now());
+ }
+
+ public virtual Quantity balance(Date date)
+ {
+ return balance(DateRange.up_to(date));
+ }
+
+ public virtual 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 virtual 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/client/server/domain/accounting/Entry.cs
@@ -0,0 +1,36 @@
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.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/client/server/domain/accounting/MCF.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace presentation.windows.server.domain.accounting
+{
+ public class MCF : SimpleUnitOfMeasure
+ {
+ public override string pretty_print(double amount)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/Potential.cs
@@ -0,0 +1,27 @@
+namespace presentation.windows.server.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/client/server/domain/accounting/PotentialEntry.cs
@@ -0,0 +1,8 @@
+namespace presentation.windows.server.domain.accounting
+{
+ public interface PotentialEntry
+ {
+ Quantity combined_with(Quantity other);
+ void commit();
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/Quantity.cs
@@ -0,0 +1,64 @@
+using System;
+
+namespace presentation.windows.server.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/client/server/domain/accounting/Range.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace presentation.windows.server.domain.accounting
+{
+ public interface Range<T> where T : IComparable<T>
+ {
+ bool includes(T item);
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/SequentialRange.cs
@@ -0,0 +1,27 @@
+using System;
+using gorilla.commons.utility;
+
+namespace presentation.windows.server.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/client/server/domain/accounting/SimpleUnitOfMeasure.cs
@@ -0,0 +1,21 @@
+namespace presentation.windows.server.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/client/server/domain/accounting/SummaryAccount.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using gorilla.commons.utility;
+using Gorilla.Commons.Utility;
+
+namespace presentation.windows.server.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/client/server/domain/accounting/Transaction.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+
+namespace presentation.windows.server.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/client/server/domain/accounting/TransactionDoesNotBalance.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace presentation.windows.server.domain.accounting
+{
+ public class TransactionDoesNotBalance : Exception
+ {
+
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/TransactionType.cs
@@ -0,0 +1,7 @@
+namespace presentation.windows.server.domain.accounting
+{
+ public interface TransactionType
+ {
+ Quantity adjust(Quantity balance, Quantity adjustment);
+ }
+}
\ No newline at end of file
product/client/server/domain/accounting/UnitOfMeasure.cs
@@ -1,4 +1,9 @@
namespace presentation.windows.server.domain.accounting
{
- public interface UnitOfMeasure {}
+ public interface UnitOfMeasure
+ {
+ double convert(double amount, UnitOfMeasure other);
+ string pretty_print(double amount);
+ }
+
}
\ No newline at end of file
product/client/server/domain/accounting/Withdrawal.cs
@@ -0,0 +1,10 @@
+namespace presentation.windows.server.domain.accounting
+{
+ public class Withdrawal : TransactionType
+ {
+ public Quantity adjust(Quantity balance, Quantity adjustment)
+ {
+ return balance.subtract(adjustment);
+ }
+ }
+}
\ No newline at end of file
product/client/server/domain/Entity.cs
@@ -10,7 +10,7 @@ namespace presentation.windows.server.domain
id = Id<Guid>.Default;
}
- public virtual Guid id { get; private set; }
+ public virtual Guid id { get; set; }
public virtual bool Equals(Entity other)
{
product/client/server/handlers/SaveNewAccountCommand.cs → product/client/server/handlers/CreateNewDetailAccountHandler.cs
@@ -5,20 +5,20 @@ using presentation.windows.server.orm;
namespace presentation.windows.server.handlers
{
- public class SaveNewAccountCommand : AbstractHandler<CreateNewDetailAccount>
+ public class CreateNewDetailAccountHandler : AbstractHandler<CreateNewDetailAccountCommand>
{
AccountRepository accounts;
ServiceBus bus;
- public SaveNewAccountCommand(AccountRepository accounts, ServiceBus bus)
+ public CreateNewDetailAccountHandler(AccountRepository accounts, ServiceBus bus)
{
this.accounts = accounts;
this.bus = bus;
}
- public override void handle(CreateNewDetailAccount item)
+ public override void handle(CreateNewDetailAccountCommand item)
{
- accounts.save(Account.New(item.account_name, Currency.named(item.currency)));
+ accounts.save(DetailAccount.New(Currency.named(item.currency)));
bus.publish<NewAccountCreated>(x => x.name = item.account_name);
}
}
product/client/server/orm/mappings/DetailAccountMapping.cs
@@ -0,0 +1,13 @@
+using FluentNHibernate.Mapping;
+using presentation.windows.server.domain.accounting;
+
+namespace presentation.windows.server.orm.mappings
+{
+ public class DetailAccountMapping : ClassMap<DetailAccount>
+ {
+ public DetailAccountMapping()
+ {
+ Id(x => x.id).GeneratedBy.GuidComb();
+ }
+ }
+}
\ No newline at end of file
product/client/server/NHibernateBootstrapper.cs
@@ -14,8 +14,8 @@ namespace presentation.windows.server
{
public class NHibernateBootstrapper : Query<ISessionFactory>
{
- private string database_path;
- private string connection_string;
+ string database_path;
+ string connection_string;
public NHibernateBootstrapper()
{
@@ -34,15 +34,18 @@ namespace presentation.windows.server
//.ShowSql()
.ProxyFactoryFactory<ProxyFactoryFactory>()
)
- .Mappings(x => { x.FluentMappings.AddFromAssemblyOf<MappingAssembly>(); })
+ .Mappings(x =>
+ {
+ x.FluentMappings.AddFromAssemblyOf<MappingAssembly>();
+ })
.ExposeConfiguration(x => export(x)).BuildSessionFactory();
}
- private void export(Configuration configuration)
+ void export(Configuration configuration)
{
+ if (File.Exists(database_path)) File.Delete(database_path);
using (var engine = new SqlCeEngine(connection_string))
{
- if (File.Exists(database_path)) File.Delete(database_path);
engine.CreateDatabase();
}
new SchemaExport(configuration).Execute(true, true, false);
product/client/server/Program.cs
@@ -27,7 +27,7 @@ namespace presentation.windows.server
catch { }
Environment.Exit(Environment.ExitCode);
};
- Bootstrapper.run();
+ ServerBootstrapper.run();
Console.ReadLine();
}
catch (Exception e)
product/client/server/server.csproj
@@ -41,7 +41,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <PlatformTarget>x86</PlatformTarget>
+ <PlatformTarget>AnyCPU</PlatformTarget>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -111,8 +111,29 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ConfigureApplicationDirectory.cs" />
+ <Compile Include="domain\accounting\AnonymousVisitor.cs" />
+ <Compile Include="domain\accounting\BOED.cs" />
+ <Compile Include="domain\accounting\Calendar.cs" />
+ <Compile Include="domain\accounting\ConversionRatio.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\Withdrawal.cs" />
<Compile Include="handlers\AddNewFamilyMemberHandler.cs" />
- <Compile Include="Bootstrapper.cs" />
+ <Compile Include="orm\mappings\DetailAccountMapping.cs" />
+ <Compile Include="ServerBootstrapper.cs" />
<Compile Include="ConfigureMappings.cs" />
<Compile Include="domain\accounting\Account.cs" />
<Compile Include="domain\accounting\Currency.cs" />
@@ -127,7 +148,7 @@
<Compile Include="domain\payroll\UnitPrice.cs" />
<Compile Include="domain\Person.cs" />
<Compile Include="handlers\FindAllFamilyHandler.cs" />
- <Compile Include="handlers\SaveNewAccountCommand.cs" />
+ <Compile Include="handlers\CreateNewDetailAccountHandler.cs" />
<Compile Include="handlers\ShutdownApplicationCommand.cs" />
<Compile Include="NHibernateBootstrapper.cs" />
<Compile Include="orm\AccountRepository.cs" />
product/client/server/Bootstrapper.cs → product/client/server/ServerBootstrapper.cs
@@ -18,7 +18,7 @@ using ISessionFactory = NHibernate.ISessionFactory;
namespace presentation.windows.server
{
- public class Bootstrapper
+ public class ServerBootstrapper
{
static public void run()
{
@@ -48,23 +48,27 @@ namespace presentation.windows.server
builder.Register<NHibernateUnitOfWorkFactory>().As<IUnitOfWorkFactory>();
builder.Register<IContext>(x => create_application_context()).SingletonScoped();
- // commanding
- //builder.Register<ContainerCommandBuilder>().As<CommandBuilder>().SingletonScoped();
- //builder.Register<AsynchronousCommandProcessor>().As<CommandProcessor>().SingletonScoped();
+ register_handlers(builder);
+ register_repositories(builder);
+
+ Resolve.the<IEnumerable<NeedStartup>>().each(x => x.run());
+ Resolve.the<CommandProcessor>().run();
+ }
+
+ static void register_handlers(ContainerBuilder builder)
+ {
builder.Register<SynchronousCommandProcessor>().As<CommandProcessor>().SingletonScoped();
builder.Register<AddNewFamilyMemberHandler>().As<Handler>();
builder.Register<FindAllFamilyHandler>().As<Handler>();
- builder.Register<SaveNewAccountCommand>().As<Handler>();
+ builder.Register<CreateNewDetailAccountHandler>().As<Handler>();
builder.Register<ShutdownApplicationCommand>().As<Handler>();
- // queries
+ }
- // repositories
+ static void register_repositories(ContainerBuilder builder)
+ {
builder.Register<NHibernatePersonRepository>().As<PersonRepository>().FactoryScoped();
builder.Register<NHibernateAccountRepository>().As<AccountRepository>().FactoryScoped();
-
- Resolve.the<IEnumerable<NeedStartup>>().each(x => x.run());
- Resolve.the<CommandProcessor>().run();
}
static IContext create_application_context()
product/commons/infrastructure/container/DependencyResolutionException.cs
@@ -6,7 +6,7 @@ namespace Gorilla.Commons.Infrastructure.Container
public class DependencyResolutionException<T> : Exception
{
public DependencyResolutionException(Exception inner_exception)
- : base("Could not resolve {0}".formatted_using(typeof (T).FullName), inner_exception)
+ : base("Could not resolve {0}".format(typeof (T).FullName), inner_exception)
{
}
}
product/commons/infrastructure/logging/TextLogger.cs
@@ -22,7 +22,7 @@ namespace Gorilla.Commons.Infrastructure.Logging
public void debug(string formatted_string, params object[] arguments)
{
writer.WriteLine("[{0}] - {1}", Thread.CurrentThread.ManagedThreadId,
- formatted_string.formatted_using(arguments));
+ formatted_string.format(arguments));
}
public void error(Exception e)
product/commons/utility/Date.cs
@@ -8,6 +8,8 @@ namespace Gorilla.Commons.Utility
public class Date : IComparable<Date>, IComparable, IEquatable<Date>
{
readonly long ticks;
+ static public readonly Date First = new Date(DateTime.MinValue);
+ static public readonly Date Last = new Date(DateTime.MaxValue);
public Date( DateTime date)
{
product/commons/utility/RegistryExtensions.cs
@@ -17,7 +17,7 @@ namespace gorilla.commons.utility
}
catch (Exception exception)
{
- throw new Exception("Could Not Find an implementation of".formatted_using(typeof (K)), exception);
+ throw new Exception("Could Not Find an implementation of".format(typeof (K)), exception);
}
}
product/commons/utility/StringExtensions.cs
@@ -2,7 +2,7 @@ namespace gorilla.commons.utility
{
static public class StringExtensions
{
- static public string formatted_using(this string formatted_string, params object[] arguments)
+ static public string format(this string formatted_string, params object[] arguments)
{
return string.Format(formatted_string, arguments);
}
@@ -19,7 +19,7 @@ namespace gorilla.commons.utility
static public string to_string<T>(this T item)
{
- return "{0}".formatted_using(item);
+ return "{0}".format(item);
}
}
}
\ No newline at end of file
product/support/unit/server/domain/accounting/BOEDSpecs.cs
@@ -0,0 +1,47 @@
+using Machine.Specifications;
+using presentation.windows.server.domain.accounting;
+
+namespace unit.server.domain.accounting
+{
+ public class BOEDSpecs
+ {
+ public abstract class concern
+ {
+ Establish context = () =>
+ {
+ sut = new BOED();
+ };
+
+ Cleanup c = () =>
+ {
+ SimpleUnitOfMeasure.provide_rate((x, y) => ConversionRatio.Default);
+ };
+
+ static protected BOED sut;
+ }
+
+ [Subject(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/support/unit/server/domain/accounting/CurrencySpecs.cs
@@ -0,0 +1,44 @@
+using Machine.Specifications;
+using presentation.windows.server.domain.accounting;
+
+namespace unit.server.domain.accounting
+{
+ public class CurrencySpecs
+ {
+ public abstract class concern
+ {
+ Cleanup cleanup = () =>
+ {
+ SimpleUnitOfMeasure.provide_rate((x, y) => ConversionRatio.Default);
+ };
+ }
+
+ [Subject(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 = () =>
+ {
+ Currency.USD.convert(1, Currency.CAD).should_be_equal_to(1.05690034);
+ };
+ }
+
+ [Subject(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 = () =>
+ {
+ Currency.CAD.convert(1.05690034d, Currency.USD).should_be_equal_to(1.0046577561938002d);
+ };
+ }
+ }
+}
\ No newline at end of file
product/support/unit/server/domain/accounting/DetailAccountSpecs.cs
@@ -0,0 +1,47 @@
+using Machine.Specifications;
+using presentation.windows.server.domain.accounting;
+
+namespace unit.server.domain.accounting
+{
+ public class DetailAccountSpecs
+ {
+ public abstract class concern
+ {
+ Establish c = () =>
+ {
+ sut = DetailAccount.New(Currency.CAD);
+ };
+
+ static protected DetailAccount sut;
+ }
+
+ [Subject(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));
+ };
+ }
+
+ [Subject(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/support/unit/server/domain/accounting/QuantitySpecs.cs
@@ -0,0 +1,37 @@
+using Machine.Specifications;
+using presentation.windows.server.domain.accounting;
+
+namespace unit.server.domain.accounting
+{
+ public class QuantitySpecs
+ {
+ public abstract class concern
+ {
+ Establish c = () =>
+ {
+ sut = new Quantity(100, Currency.CAD);
+ };
+
+ static protected Quantity sut;
+ }
+
+ [Subject(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.should_not_be_equal_to(new Quantity(1, Currency.CAD));
+ };
+
+ It should_return_false_when_they_represent_different_currencies = () =>
+ {
+ sut.should_not_be_equal_to(new Quantity(100, Currency.USD));
+ };
+ }
+ }
+}
\ No newline at end of file
product/support/unit/server/domain/accounting/SummaryAccountSpecs.cs
@@ -0,0 +1,42 @@
+using System;
+using Machine.Specifications;
+using presentation.windows.server.domain.accounting;
+
+namespace unit.server.domain.accounting
+{
+ public class SummaryAccountSpecs
+ {
+ [Subject(typeof (SummaryAccount))]
+ public class when_retrieving_the_balance_from_a_summary_account
+ {
+ Establish c = () =>
+ {
+ cash = DetailAccount.New(Currency.CAD);
+ cash.id = Guid.NewGuid();
+ credit = DetailAccount.New(Currency.CAD);
+ credit.id = Guid.NewGuid();
+
+ cash.deposit(50, Currency.CAD);
+ credit.deposit(50, Currency.CAD);
+ sut = SummaryAccount.New(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;
+ static protected SummaryAccount sut;
+ }
+ }
+}
\ No newline at end of file
product/support/unit/server/domain/accounting/TransactionSpecs.cs
@@ -0,0 +1,115 @@
+using System;
+using Machine.Specifications;
+using presentation.windows.server.domain.accounting;
+
+namespace unit.server.domain.accounting
+{
+ public class TransactionSpecs
+ {
+ public abstract class concern
+ {
+ Establish c = () =>
+ {
+ sut = Transaction.New(Currency.CAD);
+ };
+
+ static protected Transaction sut;
+ }
+
+ [Subject(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;
+ }
+
+ [Subject(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;
+ }
+
+ [Subject(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/support/unit/AssertionExtensions.cs
@@ -0,0 +1,54 @@
+using System;
+using Machine.Specifications;
+
+namespace unit
+{
+ static public class AssertionExtensions
+ {
+ static public void should_not_be_null<T>(this T item)
+ {
+ item.ShouldNotBeNull();
+ }
+
+ static public void should_be_an_instance_of<T>(this object actual)
+ {
+ actual.ShouldBeOfType(typeof (T));
+ }
+
+ static public void should_be_equal_to<T>(this T actual, T expected)
+ {
+ actual.ShouldEqual(expected);
+ }
+
+ static public void should_not_be_equal_to<T>(this T actual, T expected)
+ {
+ actual.ShouldNotEqual(expected);
+ }
+
+ static public void should_be_true(this bool actual)
+ {
+ actual.ShouldBeTrue();
+ }
+
+ static public void should_be_false(this bool actual)
+ {
+ actual.ShouldBeFalse();
+ }
+
+ static public void should_have_thrown<Exception>(this Action action) where Exception : System.Exception
+ {
+ try
+ {
+ action();
+ throw new System.Exception("Did not throw.");
+ }
+ catch (Exception e)
+ {
+ if (!e.GetType().Equals(typeof (Exception)))
+ {
+ throw new System.Exception("Threw the wrong exception");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
product/support/unit/unit.csproj
@@ -48,20 +48,35 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="AssertionExtensions.cs" />
<Compile Include="client\presenters\AddFamilyMemberPresenterSpecs.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="client\presenters\WpfCommandBuilderSpecs.cs" />
<Compile Include="Mocking.cs" />
+ <Compile Include="server\domain\accounting\BOEDSpecs.cs" />
+ <Compile Include="server\domain\accounting\CurrencySpecs.cs" />
+ <Compile Include="server\domain\accounting\DetailAccountSpecs.cs" />
+ <Compile Include="server\domain\accounting\QuantitySpecs.cs" />
+ <Compile Include="server\domain\accounting\SummaryAccountSpecs.cs" />
+ <Compile Include="server\domain\accounting\TransactionSpecs.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\client\client.ui\client.csproj">
<Project>{81E2CF6C-4D61-442E-8086-BF1E017C7041}</Project>
<Name>client %28client\client%29</Name>
</ProjectReference>
+ <ProjectReference Include="..\..\client\server\server.csproj">
+ <Project>{4E60988E-1A43-4807-8CEC-4E13F63DE363}</Project>
+ <Name>server</Name>
+ </ProjectReference>
<ProjectReference Include="..\..\commons\infrastructure\infrastructure.csproj">
<Project>{AA5EEED9-4531-45F7-AFCD-AD9717D2E405}</Project>
<Name>infrastructure</Name>
</ProjectReference>
+ <ProjectReference Include="..\..\commons\utility\utility.csproj">
+ <Project>{DD8FD29E-7424-415C-9BA3-7D9F6ECBA161}</Project>
+ <Name>utility</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />