diff --git a/Schaad.Accounting.Common/Datasets/TransactionDataset.cs b/Schaad.Accounting.Common/Datasets/TransactionDataset.cs index b080b64..9e0f7fd 100644 --- a/Schaad.Accounting.Common/Datasets/TransactionDataset.cs +++ b/Schaad.Accounting.Common/Datasets/TransactionDataset.cs @@ -33,5 +33,15 @@ namespace Schaad.Accounting.Datasets else return Value; } + + public decimal GetValue(bool withFxRate, string accountId) + { + if (accountId == OriginAccountId) + { + return -1 * GetValue(withFxRate); + } + + return GetValue(withFxRate); + } } } \ No newline at end of file diff --git a/Schaad.Accounting.Services/ChartService.cs b/Schaad.Accounting.Services/ChartService.cs index 03cf130..103ad09 100644 --- a/Schaad.Accounting.Services/ChartService.cs +++ b/Schaad.Accounting.Services/ChartService.cs @@ -112,7 +112,7 @@ namespace Schaad.Accounting.Services { var groupedByMonth = grp .GroupBy(t => t.ValueDate.Month) - .ToDictionary(g => g.Key, g => g.Sum(t => t.GetValue(account.IsFxAccount))); + .ToDictionary(g => g.Key, g => g.Sum(t => t.GetValue(account.IsFxAccount, accountId))); EnsureEntryForEveryMonth(groupedByMonth, maxMonth); list.Add(new DataSerie( @@ -132,7 +132,7 @@ namespace Schaad.Accounting.Services return transactions .GroupBy(t => t.Text) - .Select(g => new CategoryTotal(g.Key, g.Sum(t => t.GetValue(account.IsFxAccount)))) + .Select(g => new CategoryTotal(g.Key, g.Sum(t => t.GetValue(account.IsFxAccount, accountId)))) .Where(c => c.Value != 0) .ToList(); } diff --git a/Schaad.Accounting.Services/ViewService.cs b/Schaad.Accounting.Services/ViewService.cs index b59682f..abcabce 100644 --- a/Schaad.Accounting.Services/ViewService.cs +++ b/Schaad.Accounting.Services/ViewService.cs @@ -132,8 +132,9 @@ namespace Schaad.Accounting.Services } /// - /// Get transaction list filtered to a single account, with values sign-flipped - /// when the requested account is the origin of an activa booking. + /// Get transaction list filtered to a single account. Sign is not baked in here - + /// callers use with this + /// account's id to get a value that's negative when the account is paying out. /// public async Task> GetTransactionViewListAsync(string accountId) { @@ -143,22 +144,10 @@ namespace Schaad.Accounting.Services return transactionList.Select(t => new TransactionDataset( - WithDisplaySign(t), + t, accountsById[t.OriginAccountId], accountsById[t.TargetAccountId])) .ToList(); - - Transaction WithDisplaySign(Transaction t) - { - var originAccount = accountsById[t.OriginAccountId]; - if (originAccount.Class == ClassIds.Activa && accountId == t.OriginAccountId) - { - var copy = t.Clone(); - copy.Value *= -1; - return copy; - } - return t; - } } /// diff --git a/Schaad.Accounting.Tests/ChartServiceTestShould.cs b/Schaad.Accounting.Tests/ChartServiceTestShould.cs index 5e00978..1e6dcb9 100644 --- a/Schaad.Accounting.Tests/ChartServiceTestShould.cs +++ b/Schaad.Accounting.Tests/ChartServiceTestShould.cs @@ -186,11 +186,27 @@ public class ChartServiceTestShould series.Count.ShouldBe(2); var migros = series.Single(s => s.Name == "Migros"); - migros.Y[0].ShouldBe(50m); + migros.Y[0].ShouldBe(-50m); migros.Y[1].ShouldBe(0m); var coop = series.Single(s => s.Name == "Coop"); coop.Y[0].ShouldBe(0m); - coop.Y[1].ShouldBe(20m); + coop.Y[1].ShouldBe(-20m); + } + + [Fact] + public async Task ShowPositiveValueWhenAccountIsTargetOfBookingTextTransaction() + { + var checking = MakeAccount("chk", 1010); + var salary = MakeAccount("sal", 3000); + viewService.GetAccountViewAsync("chk").Returns(AsDataset(checking)); + viewService.GetTransactionViewListAsync("chk").Returns(new List + { + MakeTransaction(salary, checking, value: 5000m, valueDate: new DateTime(2026, 1, 5), text: "Lohn") + }); + + var series = await BuildService().GetBookingTextExpensesPerMonthAsync("chk"); + + series.Single().Y.Single().ShouldBe(5000m); } [Fact] @@ -206,7 +222,7 @@ public class ChartServiceTestShould var series = await BuildService().GetBookingTextExpensesPerMonthAsync("chk"); - series.Single().Y.Single().ShouldBe(200m); + series.Single().Y.Single().ShouldBe(-200m); } [Fact] @@ -225,8 +241,25 @@ public class ChartServiceTestShould var totals = await BuildService().GetBookingTextTotalsAsync("chk"); totals.Count.ShouldBe(2); - totals.ShouldContain(t => t.Name == "Migros" && t.Value == 80m); - totals.ShouldContain(t => t.Name == "Coop" && t.Value == 20m); + totals.ShouldContain(t => t.Name == "Migros" && t.Value == -80m); + totals.ShouldContain(t => t.Name == "Coop" && t.Value == -20m); + } + + [Fact] + public async Task NetOutgoingAndIncomingBookingsWithTheSameTextWhenBuildingTotalsForAccount() + { + var checking = MakeAccount("chk", 1010); + var groceries = MakeAccount("groc", 4200); + viewService.GetAccountViewAsync("chk").Returns(AsDataset(checking)); + viewService.GetTransactionViewListAsync("chk").Returns(new List + { + MakeTransaction(checking, groceries, value: 50m, valueDate: new DateTime(2026, 1, 5), text: "Migros"), + MakeTransaction(groceries, checking, value: 20m, valueDate: new DateTime(2026, 1, 6), text: "Migros") + }); + + var totals = await BuildService().GetBookingTextTotalsAsync("chk"); + + totals.Single().Value.ShouldBe(-30m); } [Fact] diff --git a/Schaad.Accounting.Tests/TransactionDatasetTestShould.cs b/Schaad.Accounting.Tests/TransactionDatasetTestShould.cs new file mode 100644 index 0000000..db931cd --- /dev/null +++ b/Schaad.Accounting.Tests/TransactionDatasetTestShould.cs @@ -0,0 +1,54 @@ +using Schaad.Accounting.Datasets; +using Schaad.Accounting.Models; +using Shouldly; + +namespace Schaad.Accounting.Tests; + +public class TransactionDatasetTestShould +{ + private static Account MakeAccount(string id) => new() { Id = id, Name = id, Currency = "CHF" }; + + private static TransactionDataset MakeTransaction(Account origin, Account target, decimal value, decimal? fxRate = null) => + new( + new Transaction + { + Id = Guid.NewGuid().ToString(), + OriginAccountId = origin.Id, + TargetAccountId = target.Id, + Value = value, + FxRate = fxRate, + ValueDate = DateTime.Now, + BookingDate = DateTime.Now + }, + origin, target); + + [Fact] + public void ReturnNegativeValueWhenAccountIsOrigin() + { + var checking = MakeAccount("chk"); + var groceries = MakeAccount("groc"); + var transaction = MakeTransaction(checking, groceries, value: 50m); + + transaction.GetValue(withFxRate: false, accountId: "chk").ShouldBe(-50m); + } + + [Fact] + public void ReturnPositiveValueWhenAccountIsTarget() + { + var checking = MakeAccount("chk"); + var groceries = MakeAccount("groc"); + var transaction = MakeTransaction(groceries, checking, value: 50m); + + transaction.GetValue(withFxRate: false, accountId: "chk").ShouldBe(50m); + } + + [Fact] + public void ApplyFxConversionBeforeApplyingSignWhenAccountIsOrigin() + { + var checking = MakeAccount("chk"); + var groceries = MakeAccount("groc"); + var transaction = MakeTransaction(checking, groceries, value: 100m, fxRate: 0.5m); + + transaction.GetValue(withFxRate: true, accountId: "chk").ShouldBe(-200m); + } +} diff --git a/Schaad.Accounting.UI/Components/Pages/Transactions.razor b/Schaad.Accounting.UI/Components/Pages/Transactions.razor index 0d168ff..c26c95e 100644 --- a/Schaad.Accounting.UI/Components/Pages/Transactions.razor +++ b/Schaad.Accounting.UI/Components/Pages/Transactions.razor @@ -62,7 +62,7 @@ @GetAccountName(context) - @context.GetValue(selectedAccount.IsFxAccount).ToString("N2") @selectedAccount.Currency + @context.GetValue(selectedAccount.IsFxAccount, selectedAccountId).ToString("N2") @selectedAccount.Currency