Skip to content

Commit

Permalink
feat(desktop): Add overview of loans to be migrated
Browse files Browse the repository at this point in the history
  • Loading branch information
VMelnalksnis committed Feb 23, 2024
1 parent f4c711a commit f1e1ba8
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 1 deletion.
4 changes: 4 additions & 0 deletions source/Gnomeshade.Avalonia.Core/DesignTime/DesignTimeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Gnomeshade.Avalonia.Core.Transactions.Controls;
using Gnomeshade.Avalonia.Core.Transactions.Links;
using Gnomeshade.Avalonia.Core.Transactions.Loans;
using Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;
using Gnomeshade.Avalonia.Core.Transactions.Purchases;
using Gnomeshade.Avalonia.Core.Transactions.Transfers;
using Gnomeshade.WebApi.Client;
Expand Down Expand Up @@ -217,6 +218,9 @@ public static class DesignTimeData
/// <summary>Gets an instance of <see cref="LicensesViewModel"/> for use during design time.</summary>
public static LicensesViewModel LicensesViewModel { get; } = CreateViewModel<LicensesViewModel>();

/// <summary>Gets an instance of <see cref="LoanMigrationViewModel"/> for use during design time.</summary>
public static LoanMigrationViewModel LoanMigrationViewModel { get; } = CreateViewModel<LoanMigrationViewModel>();

[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Expand Down
5 changes: 5 additions & 0 deletions source/Gnomeshade.Avalonia.Core/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Gnomeshade.Avalonia.Core.Products;
using Gnomeshade.Avalonia.Core.Reports;
using Gnomeshade.Avalonia.Core.Transactions;
using Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -169,6 +170,10 @@ public void SwitchToSetup()
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task SwitchToTransactionOverviewAsync() => SwitchTo<TransactionViewModel>();

/// <summary>Switches <see cref="ActiveView"/> to <see cref="LoanMigrationViewModel"/>.</summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task SwitchToLoanMigrationAsync() => SwitchTo<LoanMigrationViewModel>();

Check warning on line 175 in source/Gnomeshade.Avalonia.Core/MainWindowViewModel.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/MainWindowViewModel.cs#L175

Added line #L175 was not covered by tests

/// <summary>Event handler for <see cref="IClassicDesktopStyleApplicationLifetime.ShutdownRequested"/>.</summary>
/// <param name="sender">The object that sent the event.</param>
/// <param name="eventArgs">Event arguments.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Gnomeshade.Avalonia.Core.Products;
using Gnomeshade.Avalonia.Core.Reports;
using Gnomeshade.Avalonia.Core.Transactions;
using Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;

using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -46,5 +47,6 @@ public static IServiceCollection AddViewModels(this IServiceCollection serviceCo
.AddSingleton<OwnerViewModel>()
.AddSingleton<OwnerUpsertionViewModel>()
.AddSingleton<AboutViewModel>()
.AddSingleton<LicensesViewModel>();
.AddSingleton<LicensesViewModel>()
.AddSingleton<LoanMigrationViewModel>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2021 Valters Melnalksnis
// Licensed under the GNU Affero General Public License v3.0 or later.
// See LICENSE.txt file in the project root for full license information.

using System;
using System.Collections.Generic;

using Gnomeshade.WebApi.Models.Transactions;

namespace Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;

/// <inheritdoc />
internal sealed class LoanCounterpartyComparer : IEqualityComparer<Loan?>
{
public bool Equals(Loan? x, Loan? y)
{
if (ReferenceEquals(x, y))
{
return true;

Check warning on line 19 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanCounterpartyComparer.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanCounterpartyComparer.cs#L19

Added line #L19 was not covered by tests
}

if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
{
return false;

Check warning on line 24 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanCounterpartyComparer.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanCounterpartyComparer.cs#L24

Added line #L24 was not covered by tests
}

return
(x.IssuingCounterpartyId == y.IssuingCounterpartyId && x.ReceivingCounterpartyId == y.ReceivingCounterpartyId) ||
(x.IssuingCounterpartyId == y.ReceivingCounterpartyId && x.ReceivingCounterpartyId == y.IssuingCounterpartyId);
}

public int GetHashCode(Loan obj)
{
return HashCode.Combine(obj.IssuingCounterpartyId, obj.ReceivingCounterpartyId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2021 Valters Melnalksnis
// Licensed under the GNU Affero General Public License v3.0 or later.
// See LICENSE.txt file in the project root for full license information.

using System.Collections.Generic;

namespace Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;

/// <summary>Row for displaying how a loan will look after migrating.</summary>
public sealed class LoanMigrationRow : PropertyChangedBase
{
/// <summary>Initializes a new instance of the <see cref="LoanMigrationRow"/> class.</summary>
/// <param name="name">The name of the loan.</param>
/// <param name="issuer">The issuer of the loan.</param>
/// <param name="receiver">The receiver of the loan.</param>
/// <param name="amounts">The amount of the loan payments.</param>
/// <param name="currency">The currency code of <paramref name="amounts"/>.</param>
public LoanMigrationRow(string name, string issuer, string receiver, List<decimal> amounts, string currency)
{
Name = name;
Issuer = issuer;
Receiver = receiver;
Amounts = amounts;
Currency = currency;
}

/// <summary>Gets the name of the loan.</summary>
public string Name { get; }

Check warning on line 28 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs#L28

Added line #L28 was not covered by tests

/// <summary>Gets the issuer of the loan.</summary>
public string Issuer { get; }

Check warning on line 31 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs#L31

Added line #L31 was not covered by tests

/// <summary>Gets the receiver of the loan.</summary>
public string Receiver { get; }

Check warning on line 34 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs#L34

Added line #L34 was not covered by tests

/// <summary>Gets the loan payment amounts.</summary>
public List<decimal> Amounts { get; }

Check warning on line 37 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs#L37

Added line #L37 was not covered by tests

/// <summary>Gets the currency code of <see cref="Amounts"/>.</summary>
public string Currency { get; }

Check warning on line 40 in source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Avalonia.Core/Transactions/Loans/Migration/LoanMigrationRow.cs#L40

Added line #L40 was not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2021 Valters Melnalksnis
// Licensed under the GNU Affero General Public License v3.0 or later.
// See LICENSE.txt file in the project root for full license information.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Gnomeshade.WebApi.Client;

using NodaTime;

using PropertyChanged.SourceGenerator;

namespace Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;

/// <summary>Overview of loans to be migrated to named loans/loan payments.</summary>
public sealed partial class LoanMigrationViewModel : ViewModelBase
{
private readonly IGnomeshadeClient _gnomeshadeClient;

/// <summary>Gets a collection of all current loans.</summary>
[Notify(Setter.Private)]
private List<LoanOverview> _loans = [];

/// <summary>Gets a collection of all loan payments that will be created.</summary>
[Notify(Setter.Private)]
private List<LoanMigrationRow> _migratedLoans = [];

/// <summary>Initializes a new instance of the <see cref="LoanMigrationViewModel"/> class.</summary>
/// <param name="activityService">Service for indicating the activity of the application to the user.</param>
/// <param name="gnomeshadeClient">A strongly typed API client.</param>
public LoanMigrationViewModel(IActivityService activityService, IGnomeshadeClient gnomeshadeClient)
: base(activityService)
{
_gnomeshadeClient = gnomeshadeClient;
}

/// <inheritdoc />
protected override async Task Refresh()
{
var loans = await _gnomeshadeClient.GetLoansAsync();
var counterparties = await _gnomeshadeClient.GetCounterpartiesAsync();
var transactions = await _gnomeshadeClient.GetDetailedTransactionsAsync(new(Instant.MinValue, Instant.MaxValue));
var currencies = await _gnomeshadeClient.GetCurrenciesAsync();

MigratedLoans = loans
.OrderBy(loan =>
{
var transaction = transactions.Single(transaction => transaction.Id == loan.TransactionId);
return transaction.ValuedAt ?? transaction.BookedAt;
})
.GroupBy(loan => loan, new LoanCounterpartyComparer())
.Select(grouping =>
{
var counterparty1 = counterparties.Single(counterparty => counterparty.Id == grouping.Key.IssuingCounterpartyId);
var counterparty2 = counterparties.Single(counterparty => counterparty.Id == grouping.Key.ReceivingCounterpartyId);
return (counterparty1, counterparty2, grouping.ToArray());
})
.SelectMany(loanTuple =>
{
var issuer = loanTuple.Item3.First().IssuingCounterpartyId == loanTuple.counterparty1.Id
? loanTuple.counterparty1
: loanTuple.counterparty2;
var receiver = loanTuple.counterparty1 == issuer
? loanTuple.counterparty2
: loanTuple.counterparty1;
return loanTuple.Item3.GroupBy(loan => loan.CurrencyId).Select(grouping =>
{
var currency = currencies.Single(currency => currency.Id == grouping.Key);
var name = $"{issuer.Name} loan to {receiver.Name} ({currency.AlphabeticCode})";
return new LoanMigrationRow(name, issuer.Name, receiver.Name, grouping.Select(loan => loan.Amount).ToList(), currency.AlphabeticCode);
});
})
.ToList();

Loans = transactions
.SelectMany(transaction => transaction.Loans)
.Select(loan => new LoanOverview(
loan.Id,
counterparties.Single(counterparty => counterparty.Id == loan.IssuingCounterpartyId).Name,
counterparties.Single(counterparty => counterparty.Id == loan.ReceivingCounterpartyId).Name,
loan.Amount,
currencies.Single(currency => currency.Id == loan.CurrencyId).AlphabeticCode))
.ToList();
}
}
3 changes: 3 additions & 0 deletions source/Gnomeshade.Desktop/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
<MenuItem
x:Name="Transactions" Header="_Overview"
Command="{Binding SwitchToTransactionOverviewAsync}" />
<MenuItem
x:Name="LoanMigration" Header="Loan Migration"
Command="{Binding SwitchToLoanMigrationAsync}" />

Check warning on line 73 in source/Gnomeshade.Desktop/Views/MainWindow.axaml

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Desktop/Views/MainWindow.axaml#L71-L73

Added lines #L71 - L73 were not covered by tests
<MenuItem
x:Name="Import" Header="_Import"
Command="{Binding SwitchToImportAsync}" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dd="clr-namespace:Gnomeshade.Avalonia.Core.DesignTime;assembly=Gnomeshade.Avalonia.Core"
xmlns:migration="clr-namespace:Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;assembly=Gnomeshade.Avalonia.Core"
xmlns:loans="clr-namespace:Gnomeshade.Avalonia.Core.Transactions.Loans;assembly=Gnomeshade.Avalonia.Core"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
d:DataContext="{x:Static dd:DesignTimeData.LoanMigrationViewModel}"
x:Class="Gnomeshade.Desktop.Views.Transactions.Loans.Migration.LoanMigrationView"
x:DataType="migration:LoanMigrationViewModel">

<Grid ColumnDefinitions="Auto,Auto,Auto" RowDefinitions="Auto,*">

<TextBlock Grid.Column="0" Grid.Row="0" Text="Legacy loans" HorizontalAlignment="Center"/>
<DataGrid
Grid.Column="0" Grid.Row="1"
VerticalAlignment="Stretch"
ItemsSource="{Binding Loans, Mode=TwoWay}"
AutoGenerateColumns="False" IsReadOnly="False"
CanUserReorderColumns="True" CanUserResizeColumns="True" CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTextColumn
x:DataType="loans:LoanOverview"
Header="Issuer" IsReadOnly="True"
Binding="{Binding IssuingCounterparty, Mode=OneWay}" />
<DataGridTextColumn
x:DataType="loans:LoanOverview"
Header="Receiver" IsReadOnly="True"
Binding="{Binding ReceivingCounterparty, Mode=OneWay}" />
<DataGridTextColumn
x:DataType="loans:LoanOverview"
Header="Amount" IsReadOnly="True"
Binding="{Binding Amount, Mode=OneWay, StringFormat=\{0:N2\}}" />
<DataGridTextColumn
x:DataType="loans:LoanOverview"
Header="Currency" IsReadOnly="True"
Binding="{Binding Currency, Mode=OneWay}" />
</DataGrid.Columns>
</DataGrid>

<TextBlock Grid.Column="1" Grid.Row="0" Text="Loans with payments" HorizontalAlignment="Center"/>
<DataGrid
Grid.Column="1" Grid.Row="1"
VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible"
ItemsSource="{Binding MigratedLoans, Mode=TwoWay}"
AutoGenerateColumns="False" IsReadOnly="False"
CanUserReorderColumns="True" CanUserResizeColumns="True" CanUserSortColumns="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" IsReadOnly="True">
<DataTemplate x:DataType="migration:LoanMigrationRow">
<TextBlock
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5 0"
Text="{Binding Name, Mode=OneWay}" />

Check warning on line 56 in source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml#L55-L56

Added lines #L55 - L56 were not covered by tests
</DataTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Issuer" IsReadOnly="True">
<DataTemplate x:DataType="migration:LoanMigrationRow">
<TextBlock
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5 0"
Text="{Binding Issuer, Mode=OneWay}" />

Check warning on line 63 in source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml#L62-L63

Added lines #L62 - L63 were not covered by tests
</DataTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Receiver" IsReadOnly="True">
<DataTemplate x:DataType="migration:LoanMigrationRow">
<TextBlock
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5 0"
Text="{Binding Receiver, Mode=OneWay}" />

Check warning on line 70 in source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml#L69-L70

Added lines #L69 - L70 were not covered by tests
</DataTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Currency" IsReadOnly="True">
<DataTemplate x:DataType="migration:LoanMigrationRow">
<TextBlock
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5 0"
Text="{Binding Currency, Mode=OneWay}" />

Check warning on line 77 in source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml#L76-L77

Added lines #L76 - L77 were not covered by tests
</DataTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Payments" IsReadOnly="True">
<DataTemplate x:DataType="migration:LoanMigrationRow">
<ItemsControl ItemsSource="{Binding Amounts}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="system:Decimal">
<TextBlock
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5 0"
Text="{Binding ., Mode=OneWay, StringFormat=\{0:N2\}}" />

Check warning on line 87 in source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.Desktop/Views/Transactions/Loans/Migration/LoanMigrationView.axaml#L84-L87

Added lines #L84 - L87 were not covered by tests
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

</DataTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

<!-- <UserControl Grid.Column="2" Content="{Binding Details}" /> -->
</Grid>

</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2021 Valters Melnalksnis
// Licensed under the GNU Affero General Public License v3.0 or later.
// See LICENSE.txt file in the project root for full license information.

using Avalonia.Controls;

using Gnomeshade.Avalonia.Core;
using Gnomeshade.Avalonia.Core.Transactions.Loans.Migration;

namespace Gnomeshade.Desktop.Views.Transactions.Loans.Migration;

/// <inheritdoc cref="LoanMigrationViewModel" />
public sealed partial class LoanMigrationView : UserControl, IView<LoanMigrationView, LoanMigrationViewModel>
{
/// <summary>Initializes a new instance of the <see cref="LoanMigrationView"/> class.</summary>
public LoanMigrationView() => InitializeComponent();
}

0 comments on commit f1e1ba8

Please sign in to comment.