Compare commits

..

No commits in common. "e6d28a6e850b04e1c677128998c780b43338c3c9" and "1dcccf89b14f9a582a1bb10418b6ebf2795effb0" have entirely different histories.

6 changed files with 32 additions and 122 deletions

View file

@ -33,15 +33,5 @@ namespace Schaad.Accounting.Datasets
else else
return Value; return Value;
} }
public decimal GetValue(bool withFxRate, string accountId)
{
if (accountId == OriginAccountId)
{
return -1 * GetValue(withFxRate);
}
return GetValue(withFxRate);
}
} }
} }

View file

@ -112,7 +112,7 @@ namespace Schaad.Accounting.Services
{ {
var groupedByMonth = grp var groupedByMonth = grp
.GroupBy(t => t.ValueDate.Month) .GroupBy(t => t.ValueDate.Month)
.ToDictionary(g => g.Key, g => g.Sum(t => t.GetValue(account.IsFxAccount, accountId))); .ToDictionary(g => g.Key, g => g.Sum(t => t.GetValue(account.IsFxAccount)));
EnsureEntryForEveryMonth(groupedByMonth, maxMonth); EnsureEntryForEveryMonth(groupedByMonth, maxMonth);
list.Add(new DataSerie( list.Add(new DataSerie(
@ -131,9 +131,8 @@ namespace Schaad.Accounting.Services
var transactions = await viewService.GetTransactionViewListAsync(accountId); var transactions = await viewService.GetTransactionViewListAsync(accountId);
return transactions return transactions
.Where(t => t.TargetAccountId == accountId)
.GroupBy(t => t.Text) .GroupBy(t => t.Text)
.Select(g => new CategoryTotal(g.Key, g.Sum(t => t.GetValue(account.IsFxAccount, accountId)))) .Select(g => new CategoryTotal(g.Key, g.Sum(t => t.GetValue(account.IsFxAccount))))
.Where(c => c.Value != 0) .Where(c => c.Value != 0)
.ToList(); .ToList();
} }

View file

@ -132,9 +132,8 @@ namespace Schaad.Accounting.Services
} }
/// <summary> /// <summary>
/// Get transaction list filtered to a single account. Sign is not baked in here - /// Get transaction list filtered to a single account, with values sign-flipped
/// callers use <see cref="TransactionDataset.GetValue(bool, string)"/> with this /// when the requested account is the origin of an activa booking.
/// account's id to get a value that's negative when the account is paying out.
/// </summary> /// </summary>
public async Task<List<TransactionDataset>> GetTransactionViewListAsync(string accountId) public async Task<List<TransactionDataset>> GetTransactionViewListAsync(string accountId)
{ {
@ -144,10 +143,22 @@ namespace Schaad.Accounting.Services
return transactionList.Select(t => return transactionList.Select(t =>
new TransactionDataset( new TransactionDataset(
t, WithDisplaySign(t),
accountsById[t.OriginAccountId], accountsById[t.OriginAccountId],
accountsById[t.TargetAccountId])) accountsById[t.TargetAccountId]))
.ToList(); .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;
}
} }
/// <summary> /// <summary>

View file

@ -186,27 +186,11 @@ public class ChartServiceTestShould
series.Count.ShouldBe(2); series.Count.ShouldBe(2);
var migros = series.Single(s => s.Name == "Migros"); var migros = series.Single(s => s.Name == "Migros");
migros.Y[0].ShouldBe(-50m); migros.Y[0].ShouldBe(50m);
migros.Y[1].ShouldBe(0m); migros.Y[1].ShouldBe(0m);
var coop = series.Single(s => s.Name == "Coop"); var coop = series.Single(s => s.Name == "Coop");
coop.Y[0].ShouldBe(0m); 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<TransactionDataset>
{
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] [Fact]
@ -222,7 +206,7 @@ public class ChartServiceTestShould
var series = await BuildService().GetBookingTextExpensesPerMonthAsync("chk"); var series = await BuildService().GetBookingTextExpensesPerMonthAsync("chk");
series.Single().Y.Single().ShouldBe(-200m); series.Single().Y.Single().ShouldBe(200m);
} }
[Fact] [Fact]
@ -241,25 +225,8 @@ public class ChartServiceTestShould
var totals = await BuildService().GetBookingTextTotalsAsync("chk"); var totals = await BuildService().GetBookingTextTotalsAsync("chk");
totals.Count.ShouldBe(2); totals.Count.ShouldBe(2);
totals.ShouldContain(t => t.Name == "Migros" && t.Value == -80m); totals.ShouldContain(t => t.Name == "Migros" && t.Value == 80m);
totals.ShouldContain(t => t.Name == "Coop" && t.Value == -20m); 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<TransactionDataset>
{
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] [Fact]

View file

@ -1,54 +0,0 @@
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);
}
}

View file

@ -44,8 +44,6 @@
</FluentGridItem> </FluentGridItem>
</FluentGrid> </FluentGrid>
@if (selectedAccount.Class == ClassIds.Expenses)
{
<FluentGrid Justify="JustifyContent.FlexStart"> <FluentGrid Justify="JustifyContent.FlexStart">
<FluentGridItem xs="6"> <FluentGridItem xs="6">
<BookingTextOverTime @key="selectedAccount.Id" AccountId="@selectedAccount.Id"/> <BookingTextOverTime @key="selectedAccount.Id" AccountId="@selectedAccount.Id"/>
@ -54,7 +52,6 @@
<BookingTextBreakdown @key="selectedAccount.Id" AccountId="@selectedAccount.Id"/> <BookingTextBreakdown @key="selectedAccount.Id" AccountId="@selectedAccount.Id"/>
</FluentGridItem> </FluentGridItem>
</FluentGrid> </FluentGrid>
}
<FluentPaginator State="@pagination" SummaryTemplate="@template"/> <FluentPaginator State="@pagination" SummaryTemplate="@template"/>
<FluentDataGrid Items="@transactionList" Pagination="@pagination" RowSize="DataGridRowSize.Medium"> <FluentDataGrid Items="@transactionList" Pagination="@pagination" RowSize="DataGridRowSize.Medium">
@ -65,7 +62,7 @@
@GetAccountName(context) @GetAccountName(context)
</TemplateColumn> </TemplateColumn>
<TemplateColumn Title="Betrag" Align="Align.End"> <TemplateColumn Title="Betrag" Align="Align.End">
@context.GetValue(selectedAccount.IsFxAccount, selectedAccountId).ToString("N2") @selectedAccount.Currency @context.GetValue(selectedAccount.IsFxAccount).ToString("N2") @selectedAccount.Currency
</TemplateColumn> </TemplateColumn>
<TemplateColumn Align="Align.Center" Width="50px"> <TemplateColumn Align="Align.Center" Width="50px">
<FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral" title="Löschen" aria-label="Löschen" OnClick="@(() => DeleteAsync(context.Id))"/> <FluentButton IconEnd="@(new Icons.Regular.Size16.Delete())" Appearance="Appearance.Neutral" title="Löschen" aria-label="Löschen" OnClick="@(() => DeleteAsync(context.Id))"/>