Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moves calculations related to the monthly page to a new service #105

Merged
merged 4 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions subtrack.MAUI/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
using Microsoft.EntityFrameworkCore;
using subtrack.MAUI.Services.Abstractions;
using subtrack.MAUI.Services;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Infrastructure;
using subtrack.DAL.Migrations;
using System.Runtime.CompilerServices;
using subtrack.MAUI.Shared.JsInterop;

Expand Down Expand Up @@ -49,16 +46,17 @@ public static IServiceCollection AddSubtrackServices(this IServiceCollection ser
.AddScoped<IDateProvider, DateProvider>()
.AddScoped<ISubscriptionsCalculator, SubscriptionsCalculator>()
.AddScoped<ISettingsService, SettingsService>()
.AddScoped<IMonthlyPageCalculator, MonthlyPageCalculator>()
.AddScoped<AutoPaymentHandler>()
.AddTransient<HighlightJsInterop>();
}

private static void SeedDb(SubtrackDbContext dbContext)
{
var todayLastMonth = DateTime.Now.AddMonths(-1);
_ = dbContext.Database.EnsureDeleted();
dbContext.Database.Migrate();

var todayLastMonth = DateTime.Now.AddMonths(-1);
dbContext.Subscriptions.AddRange(
new DAL.Entities.Subscription() { Name = "paramount", LastPayment = todayLastMonth.AddDays(-1), FirstPaymentDay = todayLastMonth.AddDays(-1).Day, Cost = 3m, BillingOccurrence = DAL.Entities.BillingOccurrence.Week, BillingInterval = 2 },
new DAL.Entities.Subscription() { Name = "Disney+", LastPayment = todayLastMonth, FirstPaymentDay = todayLastMonth.Day, Cost = 3m, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 1 },
Expand Down
50 changes: 21 additions & 29 deletions subtrack.MAUI/Pages/Monthly.razor
Original file line number Diff line number Diff line change
@@ -1,45 +1,37 @@
@page "/monthly"

@inject subtrack.MAUI.Services.Abstractions.ISubscriptionService SubscriptionService
@inject subtrack.MAUI.Services.Abstractions.ISubscriptionsCalculator SubscriptionCalculator
@inject subtrack.MAUI.Services.Abstractions.IDateProvider DateProvider
@inject subtrack.MAUI.Services.Abstractions.IMonthlyPageCalculator MonthlyPageCalculator

<AddSubscriptionButton />

@foreach (var subMonth in _currentYearSubscriptionMonths)
@foreach (var subs in _monthlySubscriptions)
{
<SubscriptionMonthItem SubscriptionMonth=@subMonth />
}

<h6 class="text-center mt-3 mb-2">@(_currentMonthDate.AddYears(1).Year)</h6>

@foreach (var subMonth in _nextYearSubscriptionMonths)
{
<SubscriptionMonthItem Year=@(_currentMonthDate.AddYears(1).Year) SubscriptionMonth=@subMonth />
}

<h6 class="text-center mt-3 mb-2">@(_currentMonthDate.AddYears(2).Year)</h6>
var displayYear = subs.Key != _currentYear;
@if (displayYear)
{
<h6 class="text-center mt-3 mb-2">@(subs.Key)</h6>
}
@foreach (var subMonth in subs.Value)
{
<SubscriptionMonthItem Year=@(displayYear ? subs.Key : null) SubscriptionMonth=@subMonth />

@foreach (var subMonth in _nextYearSubscriptionMonths)
{
<SubscriptionMonthItem Year=@(_currentMonthDate.AddYears(2).Year) SubscriptionMonth=@subMonth />
}
}

@code {
IEnumerable<Subscription> _subscriptions = new List<Subscription>();
DateTime _currentMonthDate = DateTime.Now;

IEnumerable<SubscriptionsMonthResponse> _currentYearSubscriptionMonths = null!;
IEnumerable<SubscriptionsMonthResponse> _nextYearSubscriptionMonths = null!;
IEnumerable<SubscriptionsMonthResponse> _twoYearsForwardSubscriptionMonths = null!;

int _currentYear;
IDictionary<int, List<SubscriptionsMonthResponse>> _monthlySubscriptions = default!;
protected override async Task OnInitializedAsync()
{
_subscriptions = await SubscriptionService.GetAllAsync();
var subscriptions = await SubscriptionService.GetAllAsync();

var twoYearsForwardDate = _currentMonthDate.AddYears(2);
var paymentsTwoYearsForward = SubscriptionCalculator.GetMonthlySubscriptionLists(_subscriptions, _currentMonthDate.FirstDayOfMonthDate(), twoYearsForwardDate.LastDayOfMonthDate());
var fromDate = DateProvider.Today;
_currentYear = fromDate.Year;
var futureYearsToDisplay = 2;

_currentYearSubscriptionMonths = paymentsTwoYearsForward.Where(m => m.MonthDate.Year == _currentMonthDate.Year).ToList();
_nextYearSubscriptionMonths = paymentsTwoYearsForward.Where(m => m.MonthDate.Year == _currentMonthDate.AddYears(1).Year).ToList();
_twoYearsForwardSubscriptionMonths = paymentsTwoYearsForward.Where(m => m.MonthDate.Year == _currentMonthDate.AddYears(2).Year).ToList();
var toDate = fromDate.AddYears(futureYearsToDisplay);
_monthlySubscriptions = MonthlyPageCalculator.GetMonthlySubscriptionLists(subscriptions, fromDate.FirstDayOfMonthDate(), toDate.LastDayOfMonthDate());
}
}
17 changes: 17 additions & 0 deletions subtrack.MAUI/Services/Abstractions/IMonthlyPageCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using subtrack.DAL.Entities;
using subtrack.MAUI.Responses;

namespace subtrack.MAUI.Services.Abstractions
{
public interface IMonthlyPageCalculator
{
/// <summary>
/// Fetches all subscriptions that are active between the given dates grouped by month and year
/// </summary>
/// <param name="subscriptions"></param>
/// <param name="fromIncludedDate">Starting date for fetching the subscriptions</param>
/// <param name="toIncludedDate">Ending date for fetching the subscriptions</param>
/// <returns>A dictionary where key is the year and value is the list of subscriptions grouped by month for that year</returns>
IDictionary<int, List<SubscriptionsMonthResponse>> GetMonthlySubscriptionLists(IEnumerable<Subscription> subscriptions, DateTime fromIncludedDate, DateTime toIncludedDate);
}
}
11 changes: 3 additions & 8 deletions subtrack.MAUI/Services/Abstractions/ISubscriptionsCalculator.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
using subtrack.DAL.Entities;
using subtrack.MAUI.Responses;

namespace subtrack.MAUI.Services.Abstractions
{
public interface ISubscriptionsCalculator
{
int GetDueDays(Subscription subscription);

decimal GetTotalCost(IEnumerable<Subscription> subscriptions);

decimal GetYearlyCost(Subscription subscription);

/// <summary>
/// Gets lists of due subs for each month up until-including a specific month date
/// </summary>
/// <param name="subscriptions"></param>
/// <param name="toIncludedDate"></param>
/// <returns>Subscriptions due for a specific Month Date</returns>
IEnumerable<SubscriptionsMonthResponse> GetMonthlySubscriptionLists(IEnumerable<Subscription> subscriptions, DateTime fromIncludedDate, DateTime toIncludedDate);
DateTime GetNextPaymentDate(Subscription subscription);

(bool IsDue, DateTime NextPaymentDate) IsDue(Subscription subscription);

decimal GetAverageMonthlyCost(IEnumerable<Subscription> subscriptions);
Expand Down
42 changes: 42 additions & 0 deletions subtrack.MAUI/Services/MonthlyPageCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using subtrack.DAL.Entities;
using subtrack.MAUI.Responses;
using subtrack.MAUI.Services.Abstractions;

namespace subtrack.MAUI.Services
{
internal class MonthlyPageCalculator : IMonthlyPageCalculator
{
private readonly ISubscriptionsCalculator _subscriptionsCalculator;

public MonthlyPageCalculator(ISubscriptionsCalculator subscriptionsCalculator)
{
_subscriptionsCalculator = subscriptionsCalculator;
}

private IEnumerable<Subscription> GetPaymentsUntilMonth(Subscription subscription, DateTime fromIncludedDate, DateTime toIncludedDate)
{
subscription.LastPayment = _subscriptionsCalculator.GetNextPaymentDate(subscription);

while (subscription.LastPayment.Date >= fromIncludedDate.Date
&& subscription.LastPayment.Date <= toIncludedDate.Date)
{
yield return (Subscription)subscription.Clone();
subscription.LastPayment = _subscriptionsCalculator.GetNextPaymentDate(subscription);
}
}

public IDictionary<int, List<SubscriptionsMonthResponse>> GetMonthlySubscriptionLists(IEnumerable<Subscription> subscriptions, DateTime fromIncludedMonthDate, DateTime finalIncludedMonthDate)
{
return subscriptions
.SelectMany(s => GetPaymentsUntilMonth(s, fromIncludedMonthDate, finalIncludedMonthDate))
.GroupBy(s => (s.LastPayment.Year, s.LastPayment.Month))
.Select(g =>
new SubscriptionsMonthResponse
{
MonthDate = new DateTime(g.Key.Year, g.Key.Month, 1),
Subscriptions = g.OrderBy(s => s.LastPayment).ToList(),
Cost = _subscriptionsCalculator.GetTotalCost(g)
}).GroupBy(r => r.MonthDate.Year).ToDictionary(k => k.Key, v => v.ToList());
}
}
}
27 changes: 0 additions & 27 deletions subtrack.MAUI/Services/SubscriptionsCalculator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using subtrack.DAL.Entities;
using subtrack.MAUI.Responses;
using subtrack.MAUI.Services.Abstractions;
using subtrack.MAUI.Utilities;
using System.Globalization;
Expand Down Expand Up @@ -50,32 +49,6 @@ public decimal GetYearlyCost(Subscription subscription)
return subscription.Cost * yearlyPaymentsCount;
}

private IEnumerable<Subscription> GetPaymentsUntilMonth(Subscription subscription, DateTime fromIncludedDate, DateTime toIncludedDate)
{
subscription.LastPayment = GetNextPaymentDate(subscription);

while (subscription.LastPayment.Date >= fromIncludedDate.Date
&& subscription.LastPayment.Date <= toIncludedDate.Date)
{
yield return (Subscription)subscription.Clone();
subscription.LastPayment = GetNextPaymentDate(subscription);
}
}

public IEnumerable<SubscriptionsMonthResponse> GetMonthlySubscriptionLists(IEnumerable<Subscription> subscriptions, DateTime fromIncludedMonthDate, DateTime finalIncludedMonthDate)
{
return subscriptions
.SelectMany(s => GetPaymentsUntilMonth(s, fromIncludedMonthDate, finalIncludedMonthDate))
.GroupBy(s => (s.LastPayment.Year, s.LastPayment.Month))
.Select(g =>
new SubscriptionsMonthResponse
{
MonthDate = new DateTime(g.Key.Year, g.Key.Month, 1),
Subscriptions = g.OrderBy(s => s.LastPayment).ToList(),
Cost = GetTotalCost(g)
}).ToList();
}

public DateTime GetNextPaymentDate(Subscription subscription)
{
int startDay = subscription.FirstPaymentDay;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ namespace subtrack.Tests.SubscriptionCalculatorTests
{
public class GetSubscriptionListByMonthTests
{
private readonly ISubscriptionsCalculator _sut;
private readonly IDateProvider _dateTimeProvider = Substitute.For<IDateProvider>();
private readonly IMonthlyPageCalculator _sut;
private readonly int _numberOfMonths = 3;
private readonly DateTime _fromIncludedDate = new(2023, 4, 1),
_toIncludedDate;

public GetSubscriptionListByMonthTests()
{
_toIncludedDate = _fromIncludedDate.AddMonths(_numberOfMonths - 1);
_sut = new SubscriptionsCalculator(_dateTimeProvider);
_dateTimeProvider.Today.Returns(_fromIncludedDate);
var dateProvider = Substitute.For<IDateProvider>();
var subscriptionsCalculator = new SubscriptionsCalculator(dateProvider);
_sut = new MonthlyPageCalculator(subscriptionsCalculator);
}

[Theory]
Expand All @@ -32,7 +32,7 @@ public void GetSubscriptionListByMonth_MonthProvided_Returns_UnpaidSubscriptions
var collectedSubscriptions = CollectSubscriptions(result);

// Assert
Assert.Equal(_numberOfMonths, result.Count());
Assert.Equal(_numberOfMonths, result.First().Value.Count);
Assert.Equal(_numberOfMonths, collectedSubscriptions.Count(sub => sub.Id.Equals(subscription.Id)));
}

Expand All @@ -59,7 +59,7 @@ public void GetSubscriptionListByMonth_MonthProvided_ShouldNotReturn_PaidSubscri
var result = _sut.GetMonthlySubscriptionLists(subscriptions, _fromIncludedDate, _toIncludedDate);

// Assert
Assert.Equal(_numberOfMonths, result.Count());
Assert.Equal(_numberOfMonths, result.First().Value.Count);
}

[Theory]
Expand All @@ -76,7 +76,7 @@ public void GetSubscriptionListByMonth_MonthProvided_ShouldReturnWeeklySubscript
var collectedSubscriptions = CollectSubscriptions(result);

// Assert
Assert.Equal(_numberOfMonths, result.Count());
Assert.Equal(_numberOfMonths, result.First().Value.Count);
Assert.Equal(expectedNumberOfIterationsInMonth, collectedSubscriptions.Count(sub => sub.Id.Equals(subscription.Id)));
}

Expand All @@ -90,7 +90,7 @@ public void GetSubscriptionListByMonth_WeeklySub_ShouldNotContainLastPayment()
var result = _sut.GetMonthlySubscriptionLists(new[] { sub }, _fromIncludedDate, _toIncludedDate);

// Assert
Assert.Equal(2, result.Count());
Assert.Equal(2, result.First().Value.Count);
}

[Fact]
Expand All @@ -104,8 +104,8 @@ public void GetSubscriptionListByMonth_MonthProvided_SubscriptionsHavingLongerTh
var result = _sut.GetMonthlySubscriptionLists(subscriptions, _fromIncludedDate, _toIncludedDate);

// Assert
Assert.Equal(_numberOfMonths, result.Count());
Assert.Contains(result.Last().Subscriptions, (sub) => sub.Id.Equals(subscription.Id));
Assert.Equal(_numberOfMonths, result.First().Value.Count);
Assert.Contains(result.Last().Value.Last().Subscriptions, (sub) => sub.Id.Equals(subscription.Id));
}

private IEnumerable<Subscription> CreateSubscriptions()
Expand Down Expand Up @@ -202,9 +202,9 @@ private IEnumerable<Subscription> CreateSubscriptions()
};
}

private static IEnumerable<Subscription> CollectSubscriptions(IEnumerable<SubscriptionsMonthResponse> subscriptionsMonthResponses)
private static IEnumerable<Subscription> CollectSubscriptions(IDictionary<int, List<SubscriptionsMonthResponse>> subscriptionsMonthResponses)
{
return subscriptionsMonthResponses.SelectMany(response => response.Subscriptions);
return subscriptionsMonthResponses.SelectMany(response => response.Value.SelectMany(r => r.Subscriptions));
}
}
}
Loading