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

Feature/financial years #180

Merged
merged 26 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
686d8d8
chore: remove repository interfaces from Domain project
BerendWouters Jun 24, 2023
e154dd8
feat: Adds AddFinancialYearCommand #172
BerendWouters Jun 24, 2023
d0638d4
feat: Adds closing of financial year
BerendWouters Jun 24, 2023
b399468
test: Adds unit tests
BerendWouters Jun 24, 2023
bbd7fb4
test: Adds unit tests
BerendWouters Jun 24, 2023
679182a
chore: move test to own file
BerendWouters Jun 24, 2023
6dfec1c
chore: move test to own file
BerendWouters Jun 24, 2023
e975238
test: adds unit tests for editing locked transactions
BerendWouters Jun 24, 2023
d6c598e
Revert "chore: remove repository interfaces from Domain project"
BerendWouters Jun 24, 2023
c9b55ae
chore: move some files around
BerendWouters Jun 25, 2023
78a35d0
feat: Adds FinancialYearConfiguration
BerendWouters Jun 25, 2023
321cbab
feat: Moves transactions under FinancialYear domain
BerendWouters Jun 25, 2023
68171c8
chore: Remove obsolete transactionrepository
BerendWouters Jun 25, 2023
568a59c
chore: remove Transactions dbset
BerendWouters Jun 26, 2023
3e0a3b0
feat: Adds migration
BerendWouters Jun 26, 2023
40df1b4
fix: revert migration
BerendWouters Jun 26, 2023
56745b4
feat: Adds migration
BerendWouters Jun 26, 2023
70403af
feat: Adds migration to make FinancialYearId non-nullable
BerendWouters Jul 28, 2023
cdf7da3
feat: create financial year when not found
BerendWouters Jul 28, 2023
3a05175
feat: use options pattern for financial year config
BerendWouters Jul 28, 2023
6325156
feat: adds page with list of financial years
BerendWouters Jul 28, 2023
8821594
test: fix unit tests
BerendWouters Jul 28, 2023
ef07197
feat: move transaction to correct year if date changed
BerendWouters Jul 28, 2023
e9b27f2
feat: remove IsRequired and FinancialYearConfig
BerendWouters Jul 28, 2023
5e2d7e0
feat: fix flow for changing transactions
BerendWouters Jul 28, 2023
8573cd7
chore: remove unused config
BerendWouters Jul 28, 2023
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
3 changes: 3 additions & 0 deletions Commands.Test/Commands.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
109 changes: 109 additions & 0 deletions Commands.Test/FinancialYear/When_adding_a_financial_year.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Commands.Handlers.FinancialYear.AddFinancialYear;

using Domain;
using Domain.Interfaces;

using Microsoft.EntityFrameworkCore;

using Moq;

using Persistence;
using Persistence.Repositories;

using Xunit;

namespace Commands.Test.FinancialYear;

public class When_adding_a_financial_year
{
private readonly Mock<IFinancialYearRepository> _financialYearRepositoryMock;
private readonly HaSpManContext _haspmanDbContext;
BerendWouters marked this conversation as resolved.
Show resolved Hide resolved
private readonly Mock<IFinancialYearConfigurationRepository> _financialYearConfigurationMock;

public When_adding_a_financial_year()
{
_financialYearRepositoryMock = new Mock<IFinancialYearRepository>();
_financialYearConfigurationMock = new Mock<IFinancialYearConfigurationRepository>();
_haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder<HaSpManContext>()

.UseInMemoryDatabase(Guid.NewGuid().ToString()).Options);
SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _financialYearConfigurationMock.Object,
_haspmanDbContext);
}

public AddFinancialYearHandler SUT { get; set; }




public class And_no_financial_year_already_exists : When_adding_a_financial_year
{
[Fact]
public async Task It_should_add_a_new_financial_year()
{
var startDate = new DateTimeOffset(new DateTime(DateTime.Now.Year, 9, 1));
var endDate = new DateTimeOffset(new DateTime(DateTime.Now.AddYears(1).Year, 8, 31));
_financialYearRepositoryMock
.Setup(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.StartDate == startDate)))
.Verifiable();
_financialYearRepositoryMock
.Setup(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.EndDate == endDate)))
.Verifiable();

_financialYearConfigurationMock.Setup(x => x.Get(It.IsAny<CancellationToken>()))
.ReturnsAsync(new FinancialYearConfiguration(new DateTimeOffset(new DateTime(2021, 9, 1))));

await SUT.Handle(new AddFinancialYearCommand(), CancellationToken.None);

_financialYearRepositoryMock
.Verify(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.StartDate == startDate)));
_financialYearRepositoryMock
.Verify(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.EndDate == endDate)));

}


public class And_a_financial_year_already_exists : When_adding_a_financial_year
{
[Fact]
public async Task It_should_add_a_new_financial_year_following_the_last_year()
{

var startDate = new DateTimeOffset(new DateTime(2023, 9, 1));

var endDate = new DateTimeOffset(new DateTime(2024, 8, 31));
_financialYearRepositoryMock
.Setup(x =>
x.GetMostRecentAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new Domain.FinancialYear(new DateTimeOffset(new DateTime(2022, 9, 1)),
new DateTimeOffset(new DateTime(2023,8,31)), new List<Domain.Transaction>()));

_financialYearRepositoryMock
.Setup(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.StartDate == startDate)))
.Verifiable();
_financialYearRepositoryMock
.Setup(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.EndDate == endDate)))
.Verifiable();

_financialYearConfigurationMock.Setup(x => x.Get(It.IsAny<CancellationToken>()))
.ReturnsAsync(new FinancialYearConfiguration(new DateTimeOffset(new DateTime(2021, 9, 1))));

await SUT.Handle(new AddFinancialYearCommand(), CancellationToken.None);

_financialYearRepositoryMock
.Verify(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.StartDate == startDate)));
_financialYearRepositoryMock
.Verify(x =>
x.Add(It.Is<Domain.FinancialYear>(financialYear => financialYear.EndDate == endDate)));

}
}
}
}
74 changes: 74 additions & 0 deletions Commands.Test/FinancialYear/When_closing_a_financial_year.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Commands.Handlers.FinancialYear.CloseFinancialYear;

using Domain;
using Domain.Interfaces;

using FluentAssertions;

using Moq;

using Persistence.Repositories;

using Xunit;

namespace Commands.Test.FinancialYear;

public class When_closing_a_financial_year
{
private readonly Mock<IFinancialYearRepository> _financialYearRepositoryMock;

public When_closing_a_financial_year()
{
_financialYearRepositoryMock = new Mock<IFinancialYearRepository>();
SUT = new CloseFinancialYearHandler(_financialYearRepositoryMock.Object);
}

public CloseFinancialYearHandler SUT { get; set; }

[Fact]
public async Task It_should_mark_the_year_as_closed()
{
var startDate = DateTimeOffset.Now;
var financialYear = new Domain.FinancialYear(startDate, startDate.AddYears(1).AddDays(-1), new List<Domain.Transaction>());
_financialYearRepositoryMock
.Setup(x => x.GetByIdAsync(financialYear.Id, CancellationToken.None))
.ReturnsAsync(financialYear);
; await SUT.Handle(new CloseFinancialYearCommand(financialYear.Id), CancellationToken.None);

financialYear.IsClosed.Should().BeTrue();
}

[Fact]
public async Task It_should_mark_all_related_transactions_as_as_locked()
{
var startDate = DateTimeOffset.Now;
var financialYear = new Domain.FinancialYear(startDate, startDate.AddYears(1).AddDays(-1), new List<Domain.Transaction>()
{
new CreditTransaction("Random counter party", Guid.NewGuid(), 20,DateTimeOffset.Now, "A description", new List<TransactionAttachment>(), null, new List<TransactionTypeAmount>())
});
_financialYearRepositoryMock
.Setup(x => x.GetByIdAsync(financialYear.Id, CancellationToken.None))
.ReturnsAsync(financialYear);
;
await SUT.Handle(new CloseFinancialYearCommand(financialYear.Id), CancellationToken.None);

financialYear.IsClosed.Should().BeTrue();
}

[Fact]
public async Task It_should_throw_an_exception_when_no_year_is_found()
{
var newGuid = Guid.NewGuid();
var exception = await Assert.ThrowsAsync<ArgumentException>(async () =>
{
await SUT.Handle(new CloseFinancialYearCommand(newGuid), CancellationToken.None);
});
Assert.Equal($"No financial year found by Id {newGuid} (Parameter 'Id')", exception.Message);
}
}
62 changes: 62 additions & 0 deletions Commands.Test/Transaction/When_editing_a_transaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Commands.Handlers.Transaction.EditTransaction;

using Domain;
using Domain.Interfaces;

using FluentAssertions;

using MediatR;

using Moq;

using Persistence.Repositories;

using Xunit;

using AttachmentFile = Commands.Handlers.AttachmentFile;

namespace Commands.Test.Transaction;
public class When_editing_a_transaction
{
private readonly Mock<IFinancialYearRepository> _financialYearRepositoryMock;

public When_editing_a_transaction()
{
_financialYearRepositoryMock = new Mock<IFinancialYearRepository>();
var mediatorMock = new Mock<IMediator>();
SUT = new EditTransactionHandler(_financialYearRepositoryMock.Object, mediatorMock.Object);
}

public EditTransactionHandler SUT { get; set; }

[Fact]
public async Task It_should_throw_an_exception_when_locked()
{
var bankAccountId = Guid.NewGuid();

var transaction = new CreditTransaction("Random counter party", bankAccountId, 20m, DateTimeOffset.Now,
"A description", new List<TransactionAttachment>(), null, new List<TransactionTypeAmount>());
var financialYear = new Domain.FinancialYear(new DateTimeOffset(new DateTime(2022,9,1)), new DateTimeOffset(new DateTime(2023,8,31)), new List<Domain.Transaction>()
{
transaction
});
financialYear.Close();

_financialYearRepositoryMock
.Setup(x => x.GetFinancialYearByTransactionId(transaction.Id, CancellationToken.None))
.ReturnsAsync(financialYear);

var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await SUT.Handle(
new EditTransactionCommand(transaction.Id, "Random counter party name", null, bankAccountId,
DateTimeOffset.Now, "Another description", new List<TransactionTypeAmount>(),
new List<AttachmentFile>()), CancellationToken.None));

exception.Message.Should().Be("Financial year is closed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using FluentValidation;

namespace Commands.Handlers.FinancialYear.AddFinancialYear;

public record AddFinancialYearCommand() : IRequest<Guid>;


public class AddFinancialYearCommandValidator : AbstractValidator<AddFinancialYearCommand>
{
public AddFinancialYearCommandValidator()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Domain.Interfaces;

using Persistence;
using Persistence.Repositories;

namespace Commands.Handlers.FinancialYear.AddFinancialYear;

public class AddFinancialYearHandler : IRequestHandler<AddFinancialYearCommand, Guid>
{
private readonly IFinancialYearRepository _financialYearRepository;
private readonly IFinancialYearConfigurationRepository _financialYearConfigurationRepository;

private readonly HaSpManContext _haSpManContext;

public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository,
IFinancialYearConfigurationRepository financialYearConfigurationRepository,
HaSpManContext haSpManContext)
{
_financialYearRepository = financialYearRepository;
_financialYearConfigurationRepository = financialYearConfigurationRepository;
_haSpManContext = haSpManContext;
}
public async Task<Guid> Handle(AddFinancialYearCommand request, CancellationToken cancellationToken)
{
var configuration = await _financialYearConfigurationRepository.Get(cancellationToken)
?? throw new InvalidOperationException("Financial year configuration is missing");


var lastFinancialYear = await _financialYearRepository.GetMostRecentAsync(cancellationToken);

// In case this is the first year we create, assume we want it to be this year.
// Otherwise, just add a new year
var year = lastFinancialYear == null ? DateTime.Now.Year : lastFinancialYear.EndDate.Year;

var startDate = new DateTimeOffset(new DateTime(year, configuration.StartDate.Month, configuration.StartDate.Day));

var financialYear = new Domain.FinancialYear(
startDate,
startDate.AddYears(1).AddDays(-1),
new List<Domain.Transaction>());

_financialYearRepository.Add(financialYear);
await _financialYearRepository.SaveChangesAsync(cancellationToken);
return financialYear.Id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using FluentValidation;

namespace Commands.Handlers.FinancialYear.CloseFinancialYear;

public record CloseFinancialYearCommand(Guid Id) : IRequest;

public class CloseFinancialYearCommandValidator : AbstractValidator<CloseFinancialYearCommand>
{
public CloseFinancialYearCommandValidator()
{
RuleFor(x => x.Id).NotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Domain.Interfaces;

using Persistence.Repositories;

namespace Commands.Handlers.FinancialYear.CloseFinancialYear;

public class CloseFinancialYearHandler : IRequestHandler<CloseFinancialYearCommand>
{
private readonly IFinancialYearRepository _financialYearRepository;

public CloseFinancialYearHandler(IFinancialYearRepository financialYearRepository)
{
_financialYearRepository = financialYearRepository;
}
public async Task Handle(CloseFinancialYearCommand request, CancellationToken cancellationToken)
{
var financialYear = await _financialYearRepository.GetByIdAsync(request.Id, cancellationToken)
?? throw new ArgumentException($"No financial year found by Id {request.Id}", nameof(request.Id));
financialYear.Close();

await _financialYearRepository.SaveChangesAsync(cancellationToken);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using FluentValidation;

namespace Commands.Handlers.FinancialYearConfiguration;

public record SetFinancialYearConfigurationCommand(DateTimeOffset StartDate) : IRequest;


public class SetFinancialYearConfigurationCommandValidator : AbstractValidator<SetFinancialYearConfigurationCommand>
{
public SetFinancialYearConfigurationCommandValidator()
{
RuleFor(x => x.StartDate).NotEmpty();
}
}
Loading