diff --git a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs index 1d531f2..1b865ab 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyFeeHandlerTests.cs @@ -2,39 +2,15 @@ using FinancialApi.Application.Handlers; using FinancialApi.Application.Models; using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Exceptions; using FluentAssertions; using FluentAssertions.Execution; +using Microsoft.Extensions.Time.Testing; namespace FinancialApi.Application.UnitTests; public class ApplyFeeHandlerTests { - [Fact] - public void GivenValidAccounts_BasicFee_ShouldValidateEverything() - { - // Arrange - var command = new ApplyFeeCommand(1, 100); - var account = new Account(1, 100, AccountType.Debit); - var revenueAccount = new Account(99999, 0, AccountType.Credit); - var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); - - // Act - var intents = ApplyFeeHandler.Handle(command, pair); - - // Assert - using var _ = new AssertionScope(); - intents.CustomerWrite.Entity.Balance.Should() - .Be(account.Balance - command.Amount); - intents.RevenueWrite.Entity.Balance.Should() - .Be(revenueAccount.Balance + command.Amount); - intents.JournalWrite.Entity.Lines.Count.Should() - .BeGreaterThanOrEqualTo(2); - intents.Event.AccountId.Should() - .Be(account.Id); - intents.Event.Amount.Should() - .Be(command.Amount); - } - public class LiabilityAccount { [Theory] @@ -49,17 +25,18 @@ public class ApplyFeeHandlerTests ) { // Arrange + var timeProvider = new FakeTimeProvider(); var command = new ApplyFeeCommand(1, feeAmount); var account = new Account(1, startingBalance, AccountType.Liability); var revenueAccount = new Account(99999, 0, AccountType.Credit); - var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); + var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow()); // Act - var intents = ApplyFeeHandler.Handle(command, pair); + var (customerWrite, revenueWrite, journalWrite, @event) = ApplyFeeHandler.Handle(command, feeContext); // Assert using var _ = new AssertionScope(); - intents.CustomerWrite.Entity.Balance.Should() + customerWrite.Entity.Balance.Should() .Be(expectedBalance); } @@ -70,13 +47,15 @@ public class ApplyFeeHandlerTests [InlineData(1000, 1999999)] public void GivenStartingBalanceLessThanFee_ApplyFee_ShouldFail(decimal startingBalance, decimal feeAmount) { + // Arrange + var timeProvider = new FakeTimeProvider(); var command = new ApplyFeeCommand(1, feeAmount); var account = new Account(1, startingBalance, AccountType.Liability); var revenueAccount = new Account(99999, 0, AccountType.Credit); - var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); + var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow()); // Act - var act = () => ApplyFeeHandler.Handle(command, pair); + var act = () => ApplyFeeHandler.Handle(command, feeContext); // Assert using var _ = new AssertionScope(); @@ -98,17 +77,19 @@ public class ApplyFeeHandlerTests decimal expectedBalance ) { + // Arrange + var timeProvider = new FakeTimeProvider(); var command = new ApplyFeeCommand(1, feeAmount); var account = new Account(1, startingBalance, AccountType.Credit); var revenueAccount = new Account(99999, 0, AccountType.Credit); - var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); + var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow()); // Act - var intents = ApplyFeeHandler.Handle(command, pair); + var (customerWrite, revenueWrite, journalWrite, @event) = ApplyFeeHandler.Handle(command, feeContext); // Assert using var _ = new AssertionScope(); - intents.CustomerWrite.Entity.Balance.Should() + customerWrite.Entity.Balance.Should() .Be(expectedBalance); } @@ -123,17 +104,19 @@ public class ApplyFeeHandlerTests decimal expectedBalance ) { + // Arrange + var timeProvider = new FakeTimeProvider(); var command = new ApplyFeeCommand(1, feeAmount); var account = new Account(1, startingBalance, AccountType.Credit); var revenueAccount = new Account(99999, 0, AccountType.Credit); - var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); + var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow()); // Act - var intents = ApplyFeeHandler.Handle(command, pair); + var (customerWrite, revenueWrite, journalWrite, @event) = ApplyFeeHandler.Handle(command, feeContext); // Assert using var _ = new AssertionScope(); - intents.CustomerWrite.Entity.Balance.Should() + customerWrite.Entity.Balance.Should() .Be(expectedBalance); } } diff --git a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs new file mode 100644 index 0000000..731eb52 --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ReverseJournalHandlerTests.cs @@ -0,0 +1,67 @@ +using FinancialApi.Application.Commands; +using FinancialApi.Application.Events; +using FinancialApi.Application.Handlers; +using FinancialApi.Application.Models; +using FinancialApi.Domain.Entities; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.Extensions.Time.Testing; +using Wolverine.Persistence; + +namespace FinancialApi.Application.UnitTests; + +public class ReverseJournalHandlerTests +{ + [Fact] + public void GivenValidJournalEntry_Reversal_ShouldResetAccountBalances() + { + // Arrange + var timeProvider = new FakeTimeProvider(); + var expectedSourceAccountBalance = 100.0m; + var expectedDestAccountBalance = 100.0m; + var (initialSourceAccountWrite, initialDestAccountWrite, initialJournalWrite, initialEvent) = + CreateInitialTransferTransaction( + expectedSourceAccountBalance, + expectedDestAccountBalance, + 10, + timeProvider + ); + var command = new ReverseJournalCommand(initialJournalWrite.Entity.Id, "Invalid Transaction"); + var context = new JournalReversalContext( + initialJournalWrite.Entity, + [initialSourceAccountWrite.Entity, initialDestAccountWrite.Entity], + timeProvider.GetUtcNow() + ); + + // Act + var (journalWrite, accountWrites, @event) = ReverseJournalHandler.Handle(command, context); + + // Assert + using var _ = new AssertionScope(); + accountWrites[0] + .Entity.Balance.Should() + .Be(expectedSourceAccountBalance); + accountWrites[1] + .Entity.Balance.Should() + .Be(expectedDestAccountBalance); + journalWrite.Entity.Lines.Should() + .HaveCount(initialJournalWrite.Entity.Lines.Count); + } + + private (IStorageAction SourceWrite, IStorageAction DestWrite, IStorageAction + JournalWrite, FundsTransferredEvent Event) CreateInitialTransferTransaction( + decimal sourceStartingBalance, + decimal destStartingBalance, + decimal transferAmount, + TimeProvider timeProvider + ) + { + var command = new TransferFundsCommand(1, 2, transferAmount); + var sourceAccount = new Account(1, sourceStartingBalance, AccountType.Liability); + var destAccount = new Account(2, destStartingBalance, AccountType.Liability); + var transferContext = new TransferContext(sourceAccount, destAccount, timeProvider.GetUtcNow()); + + // Act + return TransferFundsHandler.Handle(command, transferContext); + } +} \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyTransferHandlerTests.cs b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/TransferFundsHandlerTests.cs similarity index 74% rename from wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyTransferHandlerTests.cs rename to wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/TransferFundsHandlerTests.cs index 4e1a960..89f4e80 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/ApplyTransferHandlerTests.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/TransferFundsHandlerTests.cs @@ -1,37 +1,38 @@ -using FinancialApi.Application.Commands; -using FinancialApi.Application.Handlers; -using FinancialApi.Application.Models; -using FinancialApi.Domain.Entities; -using FluentAssertions; -using FluentAssertions.Execution; -using Microsoft.Extensions.Time.Testing; - -namespace FinancialApi.Application.UnitTests; - -public class ApplyTransferHandlerTests -{ - public class ApplyFeeHandlerTests - { - [Fact] - public void GivenValidAccounts_BasicFee_ShouldValidateEverything() - { - // Arrange - var fakeTimeProvider = new FakeTimeProvider(); - - var command = new TransferFundsCommand(1, 2, 100); - var sourceAccount = new Account(1, 100, AccountType.Liability); - var destAccount = new Account(1, 100, AccountType.Liability); - var transferContext = new TransferContext(sourceAccount, destAccount, fakeTimeProvider.GetUtcNow()); - - // Act - var intents = TransferFundsHandler.Handle(command, transferContext); - - // Assert - using var _ = new AssertionScope(); - intents.SourceWrite.Entity.Balance.Should() - .Be(0); - intents.DestWrite.Entity.Balance.Should() - .Be(200); - } - } +using FinancialApi.Application.Commands; +using FinancialApi.Application.Handlers; +using FinancialApi.Application.Models; +using FinancialApi.Domain.Entities; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.Extensions.Time.Testing; + +namespace FinancialApi.Application.UnitTests; + +public class TransferFundsHandlerTests +{ + public class ApplyFeeHandlerTests + { + [Fact] + public void GivenValidAccounts_BasicFee_ShouldValidateEverything() + { + // Arrange + var fakeTimeProvider = new FakeTimeProvider(); + var command = new TransferFundsCommand(1, 2, 100); + var sourceAccount = new Account(1, 100, AccountType.Liability); + var destAccount = new Account(1, 100, AccountType.Liability); + var transferContext = new TransferContext(sourceAccount, destAccount, fakeTimeProvider.GetUtcNow()); + + // Act + var (sourceWrite, destWrite, journalWrite, @event) = TransferFundsHandler.Handle(command, transferContext); + + // Assert + using var _ = new AssertionScope(); + sourceWrite.Entity.Balance.Should() + .Be(0); + destWrite.Entity.Balance.Should() + .Be(200); + journalWrite.Entity.Lines.Should() + .HaveCountGreaterThanOrEqualTo(2); + } + } } \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs index 98b0827..a8ba3ff 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ApplyFeeHandler.cs @@ -23,7 +23,7 @@ public static class ApplyFeeHandler } public static ( IStorageAction CustomerWrite, IStorageAction RevenueWrite, - IStorageAction JournalWrite, FeeAppliedEvent Event ) Handle( + IStorageAction JournalWrite, FeeAppliedEvent Event ) Handle( ApplyFeeCommand cmd, FeeContext context ) diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs index ae5f200..786bd26 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/ReverseJournalHandler.cs @@ -31,11 +31,11 @@ public static class ReverseJournalHandler throw new InvalidOperationException($"Journal Entry {cmd.OriginalJournalId} does not exist"); } - public static ( IStorageAction JournalWrite, UnitOfWork AccountWrites, JournalReversedEvent + public static (IStorageAction JournalWrite, UnitOfWork AccountWrites, JournalReversedEvent Event) Handle(ReverseJournalCommand cmd, JournalReversalContext context) { var reversalJournal = BalancedJournal.CreateReversal( - context.OriginalJournalEntry, + context.OriginalBalancedJournalEntry, $"Reversal: {cmd.Reason}", context.TimeStamp ); diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs index f434e71..8db07f3 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Handlers/TransferFundsHandler.cs @@ -27,8 +27,8 @@ public static class TransferFundsHandler return new TransferContext(source, dest, timeProvider.GetUtcNow()); } - public static ( IStorageAction SourceWrite, IStorageAction DestWrite, IStorageAction - JournalWrite, FundsTransferredEvent Event ) Handle(TransferFundsCommand cmd, TransferContext context) + public static (IStorageAction SourceWrite, IStorageAction DestWrite, IStorageAction + JournalWrite, FundsTransferredEvent Event) Handle(TransferFundsCommand cmd, TransferContext context) { var updatedSource = context.Source.ApplyPosting(cmd.Amount, EntryType.Debit); var updatedDest = context.Destination.ApplyPosting(cmd.Amount, EntryType.Credit); diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Interfaces/IReversalQuery.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Interfaces/IReversalQuery.cs index d988783..d663373 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Interfaces/IReversalQuery.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Interfaces/IReversalQuery.cs @@ -4,5 +4,5 @@ namespace FinancialApi.Application.Interfaces; public interface IReversalQuery { - Task<(JournalEntry?, List?)> GetReversalDataAsync(Guid journalEntryId); + Task<(BalancedJournalEntry?, List?)> GetReversalDataAsync(Guid journalEntryId); } \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application/Models/JournalReversalContext.cs b/wolverine/a-frame-architecture/FinancialApi.Application/Models/JournalReversalContext.cs index 19627f5..9a9ed61 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application/Models/JournalReversalContext.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Application/Models/JournalReversalContext.cs @@ -3,7 +3,7 @@ using FinancialApi.Domain.Entities; namespace FinancialApi.Application.Models; public record JournalReversalContext( - JournalEntry OriginalJournalEntry, + BalancedJournalEntry OriginalBalancedJournalEntry, IReadOnlyList AffectedAccounts, DateTimeOffset TimeStamp ); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/AccountTests.cs b/wolverine/a-frame-architecture/FinancialApi.Domain.Tests/AccountTests.cs similarity index 93% rename from wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/AccountTests.cs rename to wolverine/a-frame-architecture/FinancialApi.Domain.Tests/AccountTests.cs index 43f749d..74b6809 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Application.UnitTests/AccountTests.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain.Tests/AccountTests.cs @@ -1,56 +1,57 @@ -using FinancialApi.Domain.Entities; -using FluentAssertions; - -namespace FinancialApi.Application.UnitTests; - -public class AccountTests -{ - [Theory] - [InlineData(AccountType.Credit)] - public void CreateCreditAccountType_ShouldAllowNegativeBalance(AccountType accountType) - { - var act = () => new Account(1, -100, accountType); - - act() - .Balance.Should() - .BeNegative(); - } - - [Theory] - [InlineData(AccountType.Debit)] - [InlineData(AccountType.Revenue)] - [InlineData(AccountType.Liability)] - public void CreateDebitAccountType_ShouldNotAllowNegativeBalance(AccountType accountType) - { - var act = () => new Account(1, -100, accountType); - - act.Should() - .Throw(); - } - - [Theory] - [InlineData(AccountType.Credit)] - public void GivenCreditAccountType_ShouldAllowNegativeBalance(AccountType accountType) - { - var account = new Account(1, 0, accountType); - - var updatedAccount = account.ApplyPosting(100, EntryType.Debit); - - updatedAccount.Balance.Should() - .BeNegative(); - } - - [Theory] - [InlineData(AccountType.Debit)] - [InlineData(AccountType.Revenue)] - [InlineData(AccountType.Liability)] - public void GivenDebitAccountType_ShouldNotAllowNegativeBalance(AccountType accountType) - { - var account = new Account(1, 0, accountType); - - var act = () => account.ApplyPosting(100, EntryType.Debit); - - act.Should() - .Throw(); - } +using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Exceptions; +using FluentAssertions; + +namespace FinancialApi.Domain.Tests; + +public class AccountTests +{ + [Theory] + [InlineData(AccountType.Credit)] + public void CreateCreditAccountType_ShouldAllowNegativeBalance(AccountType accountType) + { + var act = () => new Account(1, -100, accountType); + + act() + .Balance.Should() + .BeNegative(); + } + + [Theory] + [InlineData(AccountType.Debit)] + [InlineData(AccountType.Revenue)] + [InlineData(AccountType.Liability)] + public void CreateDebitAccountType_ShouldNotAllowNegativeBalance(AccountType accountType) + { + var act = () => new Account(1, -100, accountType); + + act.Should() + .Throw(); + } + + [Theory] + [InlineData(AccountType.Credit)] + public void GivenCreditAccountType_ShouldAllowNegativeBalance(AccountType accountType) + { + var account = new Account(1, 0, accountType); + + var updatedAccount = account.ApplyPosting(100, EntryType.Debit); + + updatedAccount.Balance.Should() + .BeNegative(); + } + + [Theory] + [InlineData(AccountType.Debit)] + [InlineData(AccountType.Revenue)] + [InlineData(AccountType.Liability)] + public void GivenDebitAccountType_ShouldNotAllowNegativeBalance(AccountType accountType) + { + var account = new Account(1, 0, accountType); + + var act = () => account.ApplyPosting(100, EntryType.Debit); + + act.Should() + .Throw(); + } } \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain.Tests/BalancedJournalEntryTests.cs b/wolverine/a-frame-architecture/FinancialApi.Domain.Tests/BalancedJournalEntryTests.cs new file mode 100644 index 0000000..8ae24f9 --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Domain.Tests/BalancedJournalEntryTests.cs @@ -0,0 +1,54 @@ +using FinancialApi.Domain.Aggregates; +using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Exceptions; +using FluentAssertions; +using Microsoft.Extensions.Time.Testing; + +namespace FinancialApi.Domain.Tests; + +public class BalancedJournalEntryTests +{ + [Fact] + public void GivenBalanceJournal_ShouldSucceed() + { + // Arrange + var timeProvider = new FakeTimeProvider(); + List lines = [new(1, 100, EntryType.Debit), new(1, 100, EntryType.Credit)]; + + // Act + var act = () => BalancedJournal.Create(Guid.NewGuid(), "Transfer", timeProvider.GetUtcNow(), lines); + + // Assert + act.Should() + .NotThrow(); + } + + [Fact] + public void GivenUnbalancedJournal_ShouldFail() + { + // Arrange + var timeProvider = new FakeTimeProvider(); + List lines = [new(1, 10, EntryType.Debit), new(1, 100, EntryType.Credit)]; + + // Act + var act = () => BalancedJournal.Create(Guid.NewGuid(), "Transfer", timeProvider.GetUtcNow(), lines); + + // Assert + act.Should() + .Throw(); + } + + [Fact] + public void GivenEmptyJournal_ShouldFail() + { + // Arrange + var timeProvider = new FakeTimeProvider(); + + // Act + var act = () => BalancedJournal.Create(Guid.NewGuid(), "Transfer", timeProvider.GetUtcNow(), []); + + // Assert + act.Should() + .Throw(); + } +} \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Aggregates/BalancedJournal.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Aggregates/BalancedJournal.cs index de4ccb4..8bc5af1 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Domain/Aggregates/BalancedJournal.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Aggregates/BalancedJournal.cs @@ -1,34 +1,40 @@ using FinancialApi.Domain.Entities; +using FinancialApi.Domain.Exceptions; namespace FinancialApi.Domain.Aggregates; public record BalancedJournal { - public JournalEntry Entry { get; } + public BalancedJournalEntry Entry { get; } - private BalancedJournal(JournalEntry entry) + private BalancedJournal(BalancedJournalEntry entry) { Entry = entry; } public static BalancedJournal Create(Guid id, string description, DateTimeOffset createdAt, List lines) { + if (lines.Count(l => l.Type == EntryType.Debit) == 0) + { + throw new GaapViolationException($"GAAP Violation: A journal entry needs at least one debit line."); + } + + if (lines.Count(l => l.Type == EntryType.Credit) == 0) + { + throw new GaapViolationException($"A journal entry needs at least one credit line."); + } + var debits = lines.Where(l => l.Type == EntryType.Debit) .Sum(l => l.Amount); var credits = lines.Where(l => l.Type == EntryType.Credit) .Sum(l => l.Amount); - if (debits != credits) - { - throw new InvalidOperationException( - $"GAAP Violation: Total Debits ({debits}) must equal Total Credits ({credits})!" - ); - } - - return new BalancedJournal(new JournalEntry(id, description, createdAt, lines)); + return debits != credits + ? throw new GaapViolationException($"Total Debits ({debits}) must equal Total Credits ({credits})!") + : new BalancedJournal(new BalancedJournalEntry(id, description, createdAt, lines)); } - public static BalancedJournal CreateReversal(JournalEntry original, string reason, DateTimeOffset now) + public static BalancedJournal CreateReversal(BalancedJournalEntry original, string reason, DateTimeOffset now) { var reversedLines = original.Lines.Select(l => l with { Type = l.Type == EntryType.Debit ? EntryType.Credit : EntryType.Debit } diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/Account.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/Account.cs index f275278..08c9e49 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/Account.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/Account.cs @@ -1,4 +1,6 @@ -namespace FinancialApi.Domain.Entities; +using FinancialApi.Domain.Exceptions; + +namespace FinancialApi.Domain.Entities; public enum AccountType { diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/JournalEntry.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/BalancedJournalEntry.cs similarity index 67% rename from wolverine/a-frame-architecture/FinancialApi.Domain/Entities/JournalEntry.cs rename to wolverine/a-frame-architecture/FinancialApi.Domain/Entities/BalancedJournalEntry.cs index b32bf86..df5f7ad 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/JournalEntry.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/BalancedJournalEntry.cs @@ -1,15 +1,13 @@ namespace FinancialApi.Domain.Entities; -public record JournalEntry +public record BalancedJournalEntry { public Guid Id { get; init; } public string Description { get; init; } public DateTimeOffset CreatedAt { get; init; } public List Lines { get; init; } = []; - internal JournalEntry() { } - - internal JournalEntry(Guid id, string description, DateTimeOffset createdAt, List lines) + internal BalancedJournalEntry(Guid id, string description, DateTimeOffset createdAt, List lines) { Lines = lines; Id = id; diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/InsufficientFundsException.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/InsufficientFundsException.cs deleted file mode 100644 index 96be158..0000000 --- a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/InsufficientFundsException.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace FinancialApi.Domain.Entities; - -public class InsufficientFundsException(string message) : Exception(message); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/InvalidStartingBalanceException.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/InvalidStartingBalanceException.cs deleted file mode 100644 index 7e9a058..0000000 --- a/wolverine/a-frame-architecture/FinancialApi.Domain/Entities/InvalidStartingBalanceException.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace FinancialApi.Domain.Entities; - -public class InvalidStartingBalanceException(string message) : Exception(message); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/GaapViolationException.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/GaapViolationException.cs new file mode 100644 index 0000000..9e44719 --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/GaapViolationException.cs @@ -0,0 +1,3 @@ +namespace FinancialApi.Domain.Exceptions; + +public class GaapViolationException(string message) : Exception(message); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/InsufficientFundsException.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/InsufficientFundsException.cs new file mode 100644 index 0000000..f8500be --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/InsufficientFundsException.cs @@ -0,0 +1,3 @@ +namespace FinancialApi.Domain.Exceptions; + +public class InsufficientFundsException(string message) : GaapViolationException(message); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/InvalidStartingBalanceException.cs b/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/InvalidStartingBalanceException.cs new file mode 100644 index 0000000..34dabb9 --- /dev/null +++ b/wolverine/a-frame-architecture/FinancialApi.Domain/Exceptions/InvalidStartingBalanceException.cs @@ -0,0 +1,3 @@ +namespace FinancialApi.Domain.Exceptions; + +public class InvalidStartingBalanceException(string message) : GaapViolationException(message); \ No newline at end of file diff --git a/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountDbContext.cs b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountDbContext.cs index db4bf30..ef9bf52 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountDbContext.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/AccountDbContext.cs @@ -6,7 +6,7 @@ namespace FinancialApi.Infrastructure; public class AccountDbContext(DbContextOptions options) : DbContext(options) { public DbSet Accounts => Set(); - public DbSet JournalEntries => Set(); + public DbSet JournalEntries => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -20,7 +20,7 @@ public class AccountDbContext(DbContextOptions options) : DbCo new Account(99999, 0.00m, AccountType.Revenue) ); - modelBuilder.Entity(builder => + modelBuilder.Entity(builder => { builder.HasKey(x => x.Id); builder.OwnsMany( diff --git a/wolverine/a-frame-architecture/FinancialApi.Infrastructure/ReversalQuery.cs b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/ReversalQuery.cs index abc9f84..2b0a51b 100644 --- a/wolverine/a-frame-architecture/FinancialApi.Infrastructure/ReversalQuery.cs +++ b/wolverine/a-frame-architecture/FinancialApi.Infrastructure/ReversalQuery.cs @@ -6,7 +6,7 @@ namespace FinancialApi.Infrastructure; public class ReversalQuery(AccountDbContext db) : IReversalQuery { - public async Task<(JournalEntry?, List?)> GetReversalDataAsync(Guid journalEntryId) + public async Task<(BalancedJournalEntry?, List?)> GetReversalDataAsync(Guid journalEntryId) { var entry = await db.JournalEntries.Include(x => x.Lines) .AsNoTracking() diff --git a/wolverine/a-frame-architecture/a-frame-architecture.sln b/wolverine/a-frame-architecture/a-frame-architecture.sln index 8dcd001..d825d30 100644 --- a/wolverine/a-frame-architecture/a-frame-architecture.sln +++ b/wolverine/a-frame-architecture/a-frame-architecture.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Domain", "Fina EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Application.UnitTests", "FinancialApi.Application.UnitTests\FinancialApi.Application.UnitTests.csproj", "{93DBA3AA-3C03-431C-A41C-DDADFAC964D5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Domain.Tests", "FinancialApi.Domain.Tests\FinancialApi.Domain.Tests.csproj", "{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Release|Any CPU.Build.0 = Release|Any CPU + {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/wolverine/a-frame-architecture/a-frame-architecture.sln.DotSettings.user b/wolverine/a-frame-architecture/a-frame-architecture.sln.DotSettings.user index e360ee3..576504b 100644 --- a/wolverine/a-frame-architecture/a-frame-architecture.sln.DotSettings.user +++ b/wolverine/a-frame-architecture/a-frame-architecture.sln.DotSettings.user @@ -1,9 +1,11 @@  ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution />