diff --git a/src/Money.Api.Shared/Api/Routing/CommandMapper.cs b/src/Money.Api.Shared/Api/Routing/CommandMapper.cs index a3879b79..e48936b3 100644 --- a/src/Money.Api.Shared/Api/Routing/CommandMapper.cs +++ b/src/Money.Api.Shared/Api/Routing/CommandMapper.cs @@ -18,6 +18,7 @@ public CommandMapper() Add("outcome-delete"); Add("income-create"); + Add("income-change-amount"); Add("income-delete"); Add("category-create"); diff --git a/src/Money.Api/Domain/Hubs/ApiHub.cs b/src/Money.Api/Domain/Hubs/ApiHub.cs index b2adbaf6..a2f99b57 100644 --- a/src/Money.Api/Domain/Hubs/ApiHub.cs +++ b/src/Money.Api/Domain/Hubs/ApiHub.cs @@ -27,7 +27,7 @@ public class ApiHub : Hub, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, - IEventHandler, IEventHandler, + IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler, IEventHandler { private readonly FormatterContainer formatters; @@ -133,6 +133,7 @@ private Task RaiseEvent(T payload) Task IEventHandler.HandleAsync(OutcomeWhenChanged payload) => RaiseEvent(payload); Task IEventHandler.HandleAsync(IncomeCreated payload) => RaiseEvent(payload); + Task IEventHandler.HandleAsync(IncomeAmountChanged payload) => RaiseEvent(payload); Task IEventHandler.HandleAsync(IncomeDeleted payload) => RaiseEvent(payload); Task IEventHandler.HandleAsync(PasswordChanged payload) => RaiseEvent(payload); diff --git a/src/Money.Models.Builders/IncomeBuilder.cs b/src/Money.Models.Builders/IncomeBuilder.cs index 28534bdf..2644479b 100644 --- a/src/Money.Models.Builders/IncomeBuilder.cs +++ b/src/Money.Models.Builders/IncomeBuilder.cs @@ -17,6 +17,7 @@ namespace Money.Models.Builders { public class IncomeBuilder : IEventHandler, + IEventHandler, IEventHandler, IQueryHandler, IQueryHandler> @@ -43,6 +44,20 @@ public async Task HandleAsync(IncomeCreated payload) } } + public async Task HandleAsync(IncomeAmountChanged payload) + { + using (ReadModelContext db = dbFactory.Create()) + { + IncomeEntity entity = await db.Incomes.FindAsync(payload.AggregateKey.AsGuidKey().Guid); + if (entity != null) + { + entity.Amount = payload.NewValue.Value; + entity.Currency = payload.NewValue.Currency; + await db.SaveChangesAsync(); + } + } + } + public async Task HandleAsync(IncomeDeleted payload) { using (ReadModelContext db = dbFactory.Create()) diff --git a/src/Money/Commands/ChangeIncomeAmount.cs b/src/Money/Commands/ChangeIncomeAmount.cs new file mode 100644 index 00000000..f0212255 --- /dev/null +++ b/src/Money/Commands/ChangeIncomeAmount.cs @@ -0,0 +1,41 @@ +using Neptuo; +using Neptuo.Commands; +using Neptuo.Models.Keys; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Money.Commands +{ + /// + /// Changes an of the income with . + /// + public class ChangeIncomeAmount : Command + { + /// + /// Gets a key of the income to modify. + /// + public IKey IncomeKey { get; private set; } + + /// + /// Gets a new income value. + /// + public Price Amount { get; private set; } + + /// + /// Changes an of the income with . + /// + /// A key of the income to modify. + /// A new income value. + public ChangeIncomeAmount(IKey incomeKey, Price amount) + { + Ensure.Condition.NotEmptyKey(incomeKey); + Ensure.NotNull(amount, "amount"); + IncomeKey = incomeKey; + Amount = amount; + } + } +} diff --git a/src/Money/Commands/Handlers/IncomeHandler.cs b/src/Money/Commands/Handlers/IncomeHandler.cs index 1ebc6370..ec6c737f 100644 --- a/src/Money/Commands/Handlers/IncomeHandler.cs +++ b/src/Money/Commands/Handlers/IncomeHandler.cs @@ -14,13 +14,15 @@ namespace Money.Commands.Handlers { public class IncomeHandler : AggregateRootCommandHandler, ICommandHandler>, - ICommandHandler> + ICommandHandler>, + ICommandHandler> { public IncomeHandler(IFactory> repositoryFactory) : base(repositoryFactory) { } public Task HandleAsync(Envelope envelope) => WithCommand(envelope.Body.Key).Execute(envelope, () => new Income(envelope.Body.Amount, envelope.Body.Description, envelope.Body.When)); + public Task HandleAsync(Envelope envelope) => WithCommand(envelope.Body.Key).Execute(envelope.Body.IncomeKey, envelope, model => model.ChangeAmount(envelope.Body.Amount)); public Task HandleAsync(Envelope envelope) => WithCommand(envelope.Body.Key).Execute(envelope.Body.IncomeKey, envelope, model => model.Delete()); } } diff --git a/src/Money/Events/IncomeAmountChanged.cs b/src/Money/Events/IncomeAmountChanged.cs new file mode 100644 index 00000000..a62d4fbc --- /dev/null +++ b/src/Money/Events/IncomeAmountChanged.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Money.Events +{ + /// + /// An event raised when an amount of income has changed. + /// + public class IncomeAmountChanged : UserEvent + { + /// + /// Gets an original value of the income. + /// + public Price OldValue { get; private set; } + + /// + /// Gets a new value of the income. + /// + public Price NewValue { get; private set; } + + internal IncomeAmountChanged(Price oldValue, Price newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + } +} diff --git a/src/Money/Income.cs b/src/Money/Income.cs index f50aeb13..5645a1a6 100644 --- a/src/Money/Income.cs +++ b/src/Money/Income.cs @@ -17,6 +17,7 @@ namespace Money /// public class Income : AggregateRoot, IEventHandler, + IEventHandler, IEventHandler { public bool IsDeleted { get; private set; } @@ -51,6 +52,20 @@ public Income(IKey key, IEnumerable events) : base(key, events) { } + private void EnsureNotDeleted() + { + if (IsDeleted) + throw new IncomeAlreadyDeletedException(); + } + + public void ChangeAmount(Price amount) + { + EnsureNotDeleted(); + + if (Amount != amount) + Publish(new IncomeAmountChanged(Amount, amount)); + } + public void Delete() => Publish(new IncomeDeleted()); Task IEventHandler.HandleAsync(IncomeCreated payload) @@ -63,6 +78,14 @@ Task IEventHandler.HandleAsync(IncomeCreated payload) }); } + Task IEventHandler.HandleAsync(IncomeAmountChanged payload) + { + return UpdateState(() => + { + Amount = payload.NewValue; + }); + } + Task IEventHandler.HandleAsync(IncomeDeleted payload) { return UpdateState(() => diff --git a/src/Money/IncomeAlreadyDeletedException.cs b/src/Money/IncomeAlreadyDeletedException.cs new file mode 100644 index 00000000..ec3d4572 --- /dev/null +++ b/src/Money/IncomeAlreadyDeletedException.cs @@ -0,0 +1,16 @@ +using Neptuo.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Money +{ + /// + /// An exception raised when trying to modify an income that is already deleted. + /// + public class IncomeAlreadyDeletedException : AggregateRootException + { } +}