-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(desktop): Add UI for loan payments
- Loading branch information
1 parent
9ef433d
commit c019727
Showing
31 changed files
with
1,055 additions
and
338 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// 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 System.Linq; | ||
|
||
using Gnomeshade.WebApi.Models.Accounts; | ||
using Gnomeshade.WebApi.Models.Loans; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Loans; | ||
|
||
/// <summary>Overview of a single <see cref="LoanPayment"/>.</summary> | ||
public sealed class LoanPaymentRow : PropertyChangedBase | ||
{ | ||
/// <summary>Initializes a new instance of the <see cref="LoanPaymentRow"/> class.</summary> | ||
/// <param name="payment">The payment which this row will represent.</param> | ||
/// <param name="loans">All available loans.</param> | ||
/// <param name="currencies">All available currencies.</param> | ||
public LoanPaymentRow(LoanPayment payment, IEnumerable<Loan> loans, IEnumerable<Currency> currencies) | ||
{ | ||
var loan = loans.Single(loan => loan.Id == payment.LoanId); | ||
Loan = loan.Name; | ||
Amount = payment.Amount; | ||
Interest = payment.Interest; | ||
Currency = currencies.Single(currency => currency.Id == loan.CurrencyId).AlphabeticCode; | ||
Id = payment.Id; | ||
} | ||
|
||
/// <summary>Gets the name of the loan that this payment is a part of.</summary> | ||
public string Loan { get; } | ||
|
||
/// <summary>Gets the amount that was loaned or payed back.</summary> | ||
public decimal Amount { get; } | ||
|
||
/// <summary>Gets the interest amount of this loan payment.</summary> | ||
public decimal Interest { get; } | ||
|
||
/// <summary>Gets the alphabetic code of the currency of <see cref="Amount"/> and <see cref="Interest"/>.</summary> | ||
public string Currency { get; } | ||
|
||
internal Guid Id { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// 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 System.Linq; | ||
|
||
using Gnomeshade.WebApi.Models.Accounts; | ||
using Gnomeshade.WebApi.Models.Loans; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Loans; | ||
|
||
/// <summary>Overview of a single <see cref="Loan"/>.</summary> | ||
public sealed class LoanRow : PropertyChangedBase | ||
{ | ||
/// <summary>Initializes a new instance of the <see cref="LoanRow"/> class.</summary> | ||
/// <param name="loan">The loan which this row will represent.</param> | ||
/// <param name="counterparties">All available counterparties.</param> | ||
/// <param name="currencies">All available currencies.</param> | ||
/// <param name="payments">All available loan payments.</param> | ||
public LoanRow( | ||
Loan loan, | ||
IReadOnlyCollection<Counterparty> counterparties, | ||
IEnumerable<Currency> currencies, | ||
IEnumerable<LoanPayment> payments) | ||
{ | ||
Id = loan.Id; | ||
Name = loan.Name; | ||
Issuer = counterparties.Single(counterparty => counterparty.Id == loan.IssuingCounterpartyId).Name; | ||
Receiver = counterparties.Single(counterparty => counterparty.Id == loan.ReceivingCounterpartyId).Name; | ||
Principal = loan.Principal; | ||
Currency = currencies.Single(currency => currency.Id == loan.CurrencyId).AlphabeticCode; | ||
|
||
var loanPayments = payments.Where(payment => payment.LoanId == Id).ToArray(); | ||
ActualPrincipal = loanPayments.Where(payment => payment.Amount > 0).Sum(payment => payment.Amount); | ||
PaidPrincipal = loanPayments.Where(payment => payment.Amount < 0).Sum(payment => -payment.Amount); | ||
PaidInterest = loanPayments.Where(payment => payment.Amount < 0).Sum(payment => -payment.Interest); | ||
} | ||
|
||
/// <summary>Gets the name of the loan.</summary> | ||
public string Name { get; } | ||
|
||
/// <summary>Gets the name of the issuing counterparty.</summary> | ||
public string Issuer { get; } | ||
|
||
/// <summary>Gets the name of the receiving counterparty.</summary> | ||
public string Receiver { get; } | ||
|
||
/// <summary>Gets the amount of capital originally borrowed or invested.</summary> | ||
public decimal Principal { get; } | ||
|
||
/// <summary>Gets the currency of <see cref="Principal"/>.</summary> | ||
public string Currency { get; } | ||
|
||
/// <summary>Gets the actual amount borrow as indicated by loan payments.</summary> | ||
public decimal ActualPrincipal { get; } | ||
|
||
/// <summary>Gets the amount of <see cref="Principal"/> that has been paid back.</summary> | ||
public decimal PaidPrincipal { get; } | ||
|
||
/// <summary>Gets the amount of interest paid.</summary> | ||
public decimal PaidInterest { get; } | ||
|
||
internal Guid Id { get; } | ||
} |
126 changes: 126 additions & 0 deletions
126
source/Gnomeshade.Avalonia.Core/Loans/LoanUpsertionViewModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// 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 System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
using Avalonia.Controls; | ||
|
||
using Gnomeshade.WebApi.Client; | ||
using Gnomeshade.WebApi.Models.Accounts; | ||
using Gnomeshade.WebApi.Models.Loans; | ||
using Gnomeshade.WebApi.Models.Owners; | ||
|
||
using PropertyChanged.SourceGenerator; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Loans; | ||
|
||
/// <summary>Creates or updates a single loan.</summary> | ||
public sealed partial class LoanUpsertionViewModel : UpsertionViewModel | ||
{ | ||
/// <summary>Gets a collection of all owners.</summary> | ||
[Notify(Setter.Private)] | ||
private List<Owner> _owners = []; | ||
|
||
/// <summary>Gets a collection of all counterparties.</summary> | ||
[Notify(Setter.Private)] | ||
private List<Counterparty> _counterparties = []; | ||
|
||
/// <summary>Gets a collection of all currencies.</summary> | ||
[Notify(Setter.Private)] | ||
private List<Currency> _currencies = []; | ||
|
||
/// <summary>Gets or sets the name of the loan.</summary> | ||
[Notify] | ||
private string? _name; | ||
|
||
/// <summary>Gets or sets the owner of the loan.</summary> | ||
[Notify] | ||
private Owner? _owner; | ||
|
||
/// <summary>Gets or sets the issuer of the loan.</summary> | ||
[Notify] | ||
private Counterparty? _issuer; | ||
|
||
/// <summary>Gets or sets the receiver of the loan.</summary> | ||
[Notify] | ||
private Counterparty? _receiver; | ||
|
||
/// <summary>Gets or sets the amount of capital originally borrowed or invested.</summary> | ||
[Notify] | ||
private decimal? _principal; | ||
|
||
/// <summary>Gets or sets the currency of <see cref="Principal"/>.</summary> | ||
[Notify] | ||
private Currency? _currency; | ||
|
||
/// <summary>Initializes a new instance of the <see cref="LoanUpsertionViewModel"/> 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> | ||
/// <param name="id">The id of the loan to edit.</param> | ||
public LoanUpsertionViewModel(IActivityService activityService, IGnomeshadeClient gnomeshadeClient, Guid? id) | ||
: base(activityService, gnomeshadeClient) | ||
{ | ||
Id = id; | ||
} | ||
|
||
/// <inheritdoc cref="AutoCompleteSelectors.Counterparty"/> | ||
public AutoCompleteSelector<object> CounterpartySelector => AutoCompleteSelectors.Counterparty; | ||
|
||
/// <inheritdoc cref="AutoCompleteSelectors.Currency"/> | ||
public AutoCompleteSelector<object> CurrencySelector => AutoCompleteSelectors.Currency; | ||
|
||
/// <inheritdoc cref="AutoCompleteSelectors.Owner"/> | ||
public AutoCompleteSelector<object> OwnerSelector => AutoCompleteSelectors.Owner; | ||
|
||
/// <inheritdoc /> | ||
public override bool CanSave => | ||
!string.IsNullOrWhiteSpace(Name) && | ||
Issuer is not null && | ||
Receiver is not null && | ||
Issuer.Id != Receiver.Id && | ||
Principal is not null && | ||
Currency is not null; | ||
|
||
/// <inheritdoc /> | ||
protected override async Task<Guid> SaveValidatedAsync() | ||
{ | ||
var id = Id ?? Guid.NewGuid(); | ||
var loan = new LoanCreation | ||
{ | ||
OwnerId = Owner?.Id, | ||
Name = Name!, | ||
IssuingCounterpartyId = Issuer?.Id, | ||
ReceivingCounterpartyId = Receiver?.Id, | ||
Principal = Principal, | ||
CurrencyId = Currency?.Id, | ||
}; | ||
|
||
await GnomeshadeClient.PutLoanAsync(id, loan); | ||
return id; | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override async Task Refresh() | ||
{ | ||
Owners = await GnomeshadeClient.GetOwnersAsync(); | ||
Counterparties = await GnomeshadeClient.GetCounterpartiesAsync(); | ||
Currencies = await GnomeshadeClient.GetCurrenciesAsync(); | ||
|
||
if (Id is not { } id) | ||
{ | ||
return; | ||
} | ||
|
||
var loan = await GnomeshadeClient.GetLoanAsync(id); | ||
Owner = Owners.Single(owner => owner.Id == loan.OwnerId); | ||
Name = loan.Name; | ||
Issuer = Counterparties.Single(counterparty => counterparty.Id == loan.IssuingCounterpartyId); | ||
Receiver = Counterparties.Single(counterparty => counterparty.Id == loan.ReceivingCounterpartyId); | ||
Principal = loan.Principal; | ||
Currency = Currencies.Single(currency => currency.Id == loan.CurrencyId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// 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.Linq; | ||
using System.Threading.Tasks; | ||
|
||
using Gnomeshade.WebApi.Client; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Loans; | ||
|
||
/// <summary>An overview of all loans.</summary> | ||
public sealed class LoanViewModel : OverviewViewModel<LoanRow, LoanUpsertionViewModel> | ||
{ | ||
private readonly IGnomeshadeClient _gnomeshadeClient; | ||
|
||
private LoanUpsertionViewModel _details; | ||
|
||
/// <summary>Initializes a new instance of the <see cref="LoanViewModel"/> 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 LoanViewModel(IActivityService activityService, IGnomeshadeClient gnomeshadeClient) | ||
: base(activityService) | ||
{ | ||
_gnomeshadeClient = gnomeshadeClient; | ||
|
||
_details = new(activityService, _gnomeshadeClient, null); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override LoanUpsertionViewModel Details | ||
{ | ||
get => _details; | ||
set => SetAndNotify(ref _details, value); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override async Task UpdateSelection() | ||
{ | ||
Details = new(ActivityService, _gnomeshadeClient, Selected?.Id); | ||
await Details.RefreshAsync(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override async Task DeleteAsync(LoanRow row) | ||
{ | ||
await _gnomeshadeClient.DeleteLoanAsync(row.Id); | ||
await RefreshAsync(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override async Task Refresh() | ||
{ | ||
var (loans, loanPayments, counterparties, currencies) = await ( | ||
_gnomeshadeClient.GetLoansAsync(), | ||
_gnomeshadeClient.GetLoanPaymentsAsync(), | ||
_gnomeshadeClient.GetCounterpartiesAsync(), | ||
_gnomeshadeClient.GetCurrenciesAsync()) | ||
.WhenAll(); | ||
|
||
var loanOverviews = loans | ||
.Select(loan => new LoanRow(loan, counterparties, currencies, loanPayments)) | ||
.ToArray(); | ||
|
||
Rows = new(loanOverviews); | ||
|
||
Details = new(ActivityService, _gnomeshadeClient, Selected?.Id); | ||
await Details.RefreshAsync(); | ||
} | ||
} |
Oops, something went wrong.