From 686d8d824e712752bf499fb27af599154d8a0437 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 16:35:20 +0200 Subject: [PATCH 01/26] chore: remove repository interfaces from Domain project --- .../AddBankAccount/AddBankAccountHandler.cs | 2 +- .../EditBankAccount/EditBankAccountHandler.cs | 5 +-- .../Member/AddMember/AddMemberHandler.cs | 3 +- .../DeleteMember/DeleteMemberCommand.cs | 2 +- .../Member/EditMember/EditMemberHandler.cs | 3 +- .../ExtendMembershipHandler.cs | 2 +- .../AddAttachments/AddAttachmentsCommand.cs | 3 +- .../Transaction/TransactionTypeValidator.cs | 2 -- Domain/Interfaces/IAttachmentStorage.cs | 11 ------- Domain/Interfaces/IBankAccountRepository.cs | 10 ------ Domain/Interfaces/IMemberRepository.cs | 11 ------- Domain/Transaction.cs | 31 +++++++++++++++++++ Persistence/HaSpManContext.cs | 1 + Persistence/Repositories/AttachmentStorage.cs | 10 ++++-- .../Repositories/BankAccountRepository.cs | 10 +++++- Persistence/Repositories/MemberRepository.cs | 11 ++++++- .../GetAttachment/GetAttachmentCommand.cs | 6 ++-- Web/Pages/Transactions/TransactionForm.razor | 2 +- Web/Startup.cs | 2 -- 19 files changed, 71 insertions(+), 56 deletions(-) delete mode 100644 Domain/Interfaces/IAttachmentStorage.cs delete mode 100644 Domain/Interfaces/IBankAccountRepository.cs delete mode 100644 Domain/Interfaces/IMemberRepository.cs diff --git a/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs b/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs index 7dabc9ee..c15fcb34 100644 --- a/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs +++ b/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs @@ -1,7 +1,7 @@ using Commands.Extensions; using Commands.Services; -using Domain.Interfaces; +using Persistence.Repositories; namespace Commands.Handlers.BankAccount.AddBankAccount; diff --git a/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs b/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs index a2366733..b643b298 100644 --- a/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs +++ b/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs @@ -1,9 +1,10 @@ using Commands.Extensions; +using Commands.Handlers.BankAccount.EditBankAccount; using Commands.Services; -using Domain.Interfaces; +using Persistence.Repositories; -namespace Commands.Handlers.BankAccount.EditBankAccount; +namespace Commands.Handlers.EditBankAccount; public class EditBankAccountHandler : IRequestHandler { diff --git a/Commands/Handlers/Member/AddMember/AddMemberHandler.cs b/Commands/Handlers/Member/AddMember/AddMemberHandler.cs index 5e85dfb3..08192d96 100644 --- a/Commands/Handlers/Member/AddMember/AddMemberHandler.cs +++ b/Commands/Handlers/Member/AddMember/AddMemberHandler.cs @@ -1,11 +1,10 @@ using Commands.Extensions; using Commands.Services; -using Domain.Interfaces; - using Microsoft.EntityFrameworkCore; using Persistence; +using Persistence.Repositories; namespace Commands.Handlers.Member.AddMember; diff --git a/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs b/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs index 1458b4fc..cc8f836c 100644 --- a/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs +++ b/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs @@ -1,4 +1,4 @@ -using Domain.Interfaces; +using Persistence.Repositories; namespace Commands.Handlers.Member.DeleteMember; diff --git a/Commands/Handlers/Member/EditMember/EditMemberHandler.cs b/Commands/Handlers/Member/EditMember/EditMemberHandler.cs index 11125334..71a9f153 100644 --- a/Commands/Handlers/Member/EditMember/EditMemberHandler.cs +++ b/Commands/Handlers/Member/EditMember/EditMemberHandler.cs @@ -1,11 +1,10 @@ using Commands.Extensions; using Commands.Services; -using Domain.Interfaces; - using Microsoft.EntityFrameworkCore; using Persistence; +using Persistence.Repositories; namespace Commands.Handlers.Member.EditMember; diff --git a/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs b/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs index 5ae4f804..bd9e0d6d 100644 --- a/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs +++ b/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs @@ -1,7 +1,7 @@ using Commands.Extensions; using Commands.Services; -using Domain.Interfaces; +using Persistence.Repositories; namespace Commands.Handlers.Member.ExtendMembership; diff --git a/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs b/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs index 621e91d5..0dfaeb22 100644 --- a/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs +++ b/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs @@ -1,5 +1,4 @@ using Domain; -using Domain.Interfaces; using FluentValidation; @@ -43,7 +42,7 @@ public async Task Handle(AddAttachmentsCommand request, CancellationToken { var transaction = await _transactionRepository.GetByIdAsync(request.TransactionId, cancellationToken) ?? throw new ArgumentException($"No transaction found for Id {request.TransactionId}", nameof(request.TransactionId)); - + var transactionAttachments = await StoreAttachmentsAsync(request, cancellationToken); transaction.AddAttachments(transactionAttachments); diff --git a/Commands/Handlers/Transaction/TransactionTypeValidator.cs b/Commands/Handlers/Transaction/TransactionTypeValidator.cs index c602164c..93608d8b 100644 --- a/Commands/Handlers/Transaction/TransactionTypeValidator.cs +++ b/Commands/Handlers/Transaction/TransactionTypeValidator.cs @@ -2,8 +2,6 @@ using FluentValidation; -using Types; - namespace Commands.Handlers.Transaction; public class TransactionTypeValidator : AbstractValidator diff --git a/Domain/Interfaces/IAttachmentStorage.cs b/Domain/Interfaces/IAttachmentStorage.cs deleted file mode 100644 index f18d0388..00000000 --- a/Domain/Interfaces/IAttachmentStorage.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Types; - -namespace Domain.Interfaces; - -public interface IAttachmentStorage -{ - Task AddAsync(string path, string contentType, byte[] bytes, CancellationToken cancellationToken); - - Task GetAsync(string blobUri, CancellationToken cancellationToken); - void Delete(string blobUri, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/Domain/Interfaces/IBankAccountRepository.cs b/Domain/Interfaces/IBankAccountRepository.cs deleted file mode 100644 index f79308e1..00000000 --- a/Domain/Interfaces/IBankAccountRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Domain.Interfaces; - -public interface IBankAccountRepository -{ - Task GetByIdAsync(Guid id, CancellationToken ct); - Task> GetAllAsync(CancellationToken ct); - void Add(BankAccount account); - void Remove(BankAccount account); - Task SaveAsync(CancellationToken ct); -} diff --git a/Domain/Interfaces/IMemberRepository.cs b/Domain/Interfaces/IMemberRepository.cs deleted file mode 100644 index 202951f5..00000000 --- a/Domain/Interfaces/IMemberRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Domain.Interfaces; - -public interface IMemberRepository -{ - Task GetById(Guid id); - Task GetByEmail(string email); - Task> GetAllAsync(); - void Add(Member member); - void Remove(Member member); - Task Save(CancellationToken cancellationToken); -} diff --git a/Domain/Transaction.cs b/Domain/Transaction.cs index 3bf3dcb9..6c55409d 100644 --- a/Domain/Transaction.cs +++ b/Domain/Transaction.cs @@ -61,6 +61,10 @@ protected Transaction( public void ChangeCounterParty(string counterPartyName, Guid? memberId) { + if (Locked) + { + throw new Exception("Transaction is locked"); + } if (memberId == MemberId && counterPartyName == CounterPartyName) { return; @@ -72,6 +76,10 @@ public void ChangeCounterParty(string counterPartyName, Guid? memberId) public void ChangeBankAccountId(Guid bankAccountId) { + if (Locked) + { + throw new Exception("Transaction is locked"); + } if (bankAccountId == BankAccountId) { return; @@ -82,6 +90,10 @@ public void ChangeBankAccountId(Guid bankAccountId) public void ChangeReceivedDateTime(DateTimeOffset receivedDateTime) { + if (Locked) + { + throw new Exception("Transaction is locked"); + } if (receivedDateTime > DateTimeOffset.Now) { throw new ArgumentException($"Received date is set to be in the future: {receivedDateTime}", @@ -98,6 +110,10 @@ public void ChangeReceivedDateTime(DateTimeOffset receivedDateTime) public void ChangeAmount(decimal amount, ICollection transactionTypeAmounts) { + if (Locked) + { + throw new Exception("Transaction is locked"); + } var sumOfTransactionTypeAmounts = transactionTypeAmounts.Sum(x => x.Amount); if (amount != sumOfTransactionTypeAmounts) { @@ -116,6 +132,10 @@ public void ChangeAmount(decimal amount, ICollection tran public void ChangeDescription(string description) { + if (Locked) + { + throw new Exception("Transaction is locked"); + } if (description == Description) { return; @@ -126,11 +146,22 @@ public void ChangeDescription(string description) public void AddAttachments(ICollection attachments) { + if (Locked) + { + throw new Exception("Transaction is locked"); + } foreach (var attachment in attachments) { Attachments.Add(attachment); } } + + public void Lock() + { + Locked = true; + } + + public bool Locked { get; set; } } public class DebitTransaction : Transaction { diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index 124fcf64..aba776dd 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -22,6 +22,7 @@ public HaSpManContext(DbContextOptions options) public DbSet BankAccounts { get; set; } = null!; public DbSet BankAccountsWithTotals { get; set; } = null!; public DbSet Transactions { get; set; } = null!; + public DbSet FinancialYears { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable("__EFMigrationsHistory", Schema.HaSpMan)); diff --git a/Persistence/Repositories/AttachmentStorage.cs b/Persistence/Repositories/AttachmentStorage.cs index a79cf639..2f05a499 100644 --- a/Persistence/Repositories/AttachmentStorage.cs +++ b/Persistence/Repositories/AttachmentStorage.cs @@ -2,8 +2,6 @@ using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; -using Domain.Interfaces; - using Microsoft.Extensions.Options; using Persistence.Configuration; @@ -50,3 +48,11 @@ public void Delete(string blobUri, CancellationToken cancellationToken) throw new NotImplementedException(); } } + +public interface IAttachmentStorage +{ + Task AddAsync(string path, string contentType, byte[] bytes, CancellationToken cancellationToken); + + Task GetAsync(string blobUri, CancellationToken cancellationToken); + void Delete(string blobUri, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Persistence/Repositories/BankAccountRepository.cs b/Persistence/Repositories/BankAccountRepository.cs index 45d37cf1..31c00e62 100644 --- a/Persistence/Repositories/BankAccountRepository.cs +++ b/Persistence/Repositories/BankAccountRepository.cs @@ -1,5 +1,4 @@ using Domain; -using Domain.Interfaces; using Microsoft.EntityFrameworkCore; @@ -41,3 +40,12 @@ public async Task SaveAsync(CancellationToken ct) await _context.SaveChangesAsync(ct); } } + +public interface IBankAccountRepository +{ + Task GetByIdAsync(Guid id, CancellationToken ct); + Task> GetAllAsync(CancellationToken ct); + void Add(BankAccount account); + void Remove(BankAccount account); + Task SaveAsync(CancellationToken ct); +} diff --git a/Persistence/Repositories/MemberRepository.cs b/Persistence/Repositories/MemberRepository.cs index 0cce1b75..2eefecc0 100644 --- a/Persistence/Repositories/MemberRepository.cs +++ b/Persistence/Repositories/MemberRepository.cs @@ -1,5 +1,4 @@ using Domain; -using Domain.Interfaces; using Microsoft.EntityFrameworkCore; @@ -45,3 +44,13 @@ public async Task Save(CancellationToken cancellationToken) } } + +public interface IMemberRepository +{ + Task GetById(Guid id); + Task GetByEmail(string email); + Task> GetAllAsync(); + void Add(Member member); + void Remove(Member member); + Task Save(CancellationToken cancellationToken); +} diff --git a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs index d64e5525..0a4cb773 100644 --- a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs +++ b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs @@ -1,10 +1,8 @@ -using Domain.Interfaces; - -using Persistence.Repositories; +using Persistence.Repositories; using Types; -namespace Commands.Handlers.Transaction.GetAttachment; +namespace Queries.Transactions.GetAttachment; public record GetAttachmentQuery(Guid TransactionId, string FileName) : IRequest; diff --git a/Web/Pages/Transactions/TransactionForm.razor b/Web/Pages/Transactions/TransactionForm.razor index f4fd3b03..8de63217 100644 --- a/Web/Pages/Transactions/TransactionForm.razor +++ b/Web/Pages/Transactions/TransactionForm.razor @@ -4,7 +4,7 @@ @using Queries.Members.Handlers.GetMemberById @using Queries.Members.ViewModels @using Queries.Members.Handlers.AutocompleteMember -@using Commands.Handlers.Transaction.GetAttachment +@using Queries.Transactions.GetAttachment @inject IWebHostEnvironment Environment @inject IDialogService dialogService diff --git a/Web/Startup.cs b/Web/Startup.cs index fda45774..d50321d4 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -1,8 +1,6 @@ using Commands.Handlers.Transaction.AddDebitTransaction; using Commands.Services; -using Domain.Interfaces; - using FluentValidation; using MediatR; From e154dd887a967d2fee2d2cf88f9bc1daa2f4ad90 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 16:36:30 +0200 Subject: [PATCH 02/26] feat: Adds AddFinancialYearCommand #172 --- .../FinancialYear/AddFinancialYearCommand.cs | 14 ++++++ .../FinancialYear/AddFinancialYearHandler.cs | 36 +++++++++++++++ Domain/FinancialYear.cs | 44 +++++++++++++++++++ .../Repositories/FinancialYearRepository.cs | 31 +++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs create mode 100644 Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs create mode 100644 Domain/FinancialYear.cs create mode 100644 Persistence/Repositories/FinancialYearRepository.cs diff --git a/Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs b/Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs new file mode 100644 index 00000000..3ad8f05b --- /dev/null +++ b/Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace Commands.Handlers.FinancialYear; + +public record AddFinancialYearCommand(DateOnly StartDate) : IRequest; + + +public class AddFinancialYearCommandValidator : AbstractValidator +{ + public AddFinancialYearCommandValidator() + { + RuleFor(x => x.StartDate).NotEmpty(); + } +} \ No newline at end of file diff --git a/Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs new file mode 100644 index 00000000..52cf37b2 --- /dev/null +++ b/Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; + +using Persistence; +using Persistence.Repositories; + +namespace Commands.Handlers.FinancialYear; + +public class AddFinancialYearHandler : IRequestHandler +{ + private readonly IFinancialYearRepository _financialYearRepository; + private readonly HaSpManContext _haSpManContext; + + public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository, HaSpManContext haSpManContext) + { + _financialYearRepository = financialYearRepository; + _haSpManContext = haSpManContext; + } + public async Task Handle(AddFinancialYearCommand request, CancellationToken cancellationToken) + { + await EnsureStartDateNotInExistingFinancialYear(request, cancellationToken); + var financialYear = new Domain.FinancialYear(request.StartDate, new List()); + _financialYearRepository.Add(financialYear); + await _financialYearRepository.SaveChangesAsync(cancellationToken); + return financialYear.Id; + } + + private async Task EnsureStartDateNotInExistingFinancialYear(AddFinancialYearCommand request, CancellationToken cancellationToken) + { + var financialYearAlreadyInExistingYear = await _haSpManContext.FinancialYears + .AsNoTracking() + .AnyAsync(x => x.EndDate <= request.StartDate, cancellationToken); + + if (financialYearAlreadyInExistingYear) + throw new InvalidOperationException("Start date of year is in already existing year"); + } +} \ No newline at end of file diff --git a/Domain/FinancialYear.cs b/Domain/FinancialYear.cs new file mode 100644 index 00000000..7ae01299 --- /dev/null +++ b/Domain/FinancialYear.cs @@ -0,0 +1,44 @@ +namespace Domain; + +public class FinancialYear +{ +#pragma warning disable CS8618 + public FinancialYear(){ } // Make EFCore happy +#pragma warning restore CS8618 + public FinancialYear(DateOnly startDate, ICollection transactions) + { + StartDate = startDate; + EndDate = startDate.AddYears(1).AddDays(-1); + Transactions = transactions; + } + + public Guid Id { get; private set; } + public DateOnly StartDate { get; set; } + public DateOnly EndDate { get; private set;} + + public bool IsClosed { get; set; } + + public ICollection Transactions { get; set; } + + public void Close() + { + if (IsClosed) + { + throw new Exception("Financial year is already closed"); + } + IsClosed = true; + foreach (var transaction in Transactions) + { + transaction.Lock(); + } + } + + public void AddTransaction(Transaction transaction) + { + if (IsClosed) + { + throw new Exception("Financial year is closed"); + } + Transactions.Add(transaction); + } +} \ No newline at end of file diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs new file mode 100644 index 00000000..161e1c73 --- /dev/null +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories; + +public class FinancialYearRepository : IFinancialYearRepository +{ + + private readonly HaSpManContext _context; + + public FinancialYearRepository(IDbContextFactory contextFactory) + { + + _context = contextFactory.CreateDbContext(); + } + + public void Add(Domain.FinancialYear financialYear) + { + _context.FinancialYears.Add(financialYear); + } + + public async Task SaveChangesAsync(CancellationToken cancellationToken) + { + await _context.SaveChangesAsync(cancellationToken); + } +} + +public interface IFinancialYearRepository +{ + void Add(Domain.FinancialYear financialYear); + Task SaveChangesAsync(CancellationToken cancellationToken); +} \ No newline at end of file From d0638d4ad452902506f69d20e7cec12f42566360 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 16:45:45 +0200 Subject: [PATCH 03/26] feat: Adds closing of financial year --- .../AddFinancialYearCommand.cs | 2 +- .../AddFinancialYearHandler.cs | 2 +- .../CloseFinancialYearCommand.cs | 19 ++++++++++++++++ .../CloseFinancialYearHandler.cs | 22 +++++++++++++++++++ .../Repositories/FinancialYearRepository.cs | 14 +++++++++--- 5 files changed, 54 insertions(+), 5 deletions(-) rename Commands/Handlers/FinancialYear/{ => AddFinancialYear}/AddFinancialYearCommand.cs (83%) rename Commands/Handlers/FinancialYear/{ => AddFinancialYear}/AddFinancialYearHandler.cs (96%) create mode 100644 Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearCommand.cs create mode 100644 Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs diff --git a/Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs similarity index 83% rename from Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs rename to Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs index 3ad8f05b..7d858065 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYearCommand.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs @@ -1,6 +1,6 @@ using FluentValidation; -namespace Commands.Handlers.FinancialYear; +namespace Commands.Handlers.FinancialYear.AddFinancialYear; public record AddFinancialYearCommand(DateOnly StartDate) : IRequest; diff --git a/Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs similarity index 96% rename from Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs rename to Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs index 52cf37b2..82bd4e10 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs @@ -3,7 +3,7 @@ using Persistence; using Persistence.Repositories; -namespace Commands.Handlers.FinancialYear; +namespace Commands.Handlers.FinancialYear.AddFinancialYear; public class AddFinancialYearHandler : IRequestHandler { diff --git a/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearCommand.cs b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearCommand.cs new file mode 100644 index 00000000..7d467c30 --- /dev/null +++ b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearCommand.cs @@ -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 +{ + public CloseFinancialYearCommandValidator() + { + RuleFor(x => x.Id).NotEmpty(); + } +} \ No newline at end of file diff --git a/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs new file mode 100644 index 00000000..c7fab9a8 --- /dev/null +++ b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs @@ -0,0 +1,22 @@ +using Persistence.Repositories; + +namespace Commands.Handlers.FinancialYear.CloseFinancialYear; + +public class CloseFinancialYearHandler : IRequestHandler +{ + private readonly IFinancialYearRepository _financialYearRepository; + + public CloseFinancialYearHandler(IFinancialYearRepository financialYearRepository) + { + _financialYearRepository = financialYearRepository; + } + public async Task Handle(CloseFinancialYearCommand request, CancellationToken cancellationToken) + { + var financialYear = await _financialYearRepository.GetById(request.Id, cancellationToken) + ?? throw new ArgumentException($"No financial year found by Id {request.Id}", nameof(request.Id)); + financialYear.Close(); + + await _financialYearRepository.SaveChangesAsync(cancellationToken); + + } +} \ No newline at end of file diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs index 161e1c73..9e05ea1f 100644 --- a/Persistence/Repositories/FinancialYearRepository.cs +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -1,4 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using Domain; + +using Microsoft.EntityFrameworkCore; namespace Persistence.Repositories; @@ -13,7 +15,12 @@ public FinancialYearRepository(IDbContextFactory contextFactory) _context = contextFactory.CreateDbContext(); } - public void Add(Domain.FinancialYear financialYear) + public Task GetById(Guid id, CancellationToken cancellationToken) + { + return _context.FinancialYears.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); + } + + public void Add(FinancialYear financialYear) { _context.FinancialYears.Add(financialYear); } @@ -26,6 +33,7 @@ public async Task SaveChangesAsync(CancellationToken cancellationToken) public interface IFinancialYearRepository { - void Add(Domain.FinancialYear financialYear); + Task GetById(Guid id, CancellationToken cancellationToken); + void Add(FinancialYear financialYear); Task SaveChangesAsync(CancellationToken cancellationToken); } \ No newline at end of file From b399468f3dd817753d158bf2e518789650a06d4e Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 17:28:52 +0200 Subject: [PATCH 04/26] test: Adds unit tests --- Commands.Test/Commands.Test.csproj | 3 +++ Persistence/HaSpManContext.cs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Commands.Test/Commands.Test.csproj b/Commands.Test/Commands.Test.csproj index 5c83fd9f..4e91c910 100644 --- a/Commands.Test/Commands.Test.csproj +++ b/Commands.Test/Commands.Test.csproj @@ -6,7 +6,10 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index aba776dd..e2238c87 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -25,7 +25,13 @@ public HaSpManContext(DbContextOptions options) public DbSet FinancialYears { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable("__EFMigrationsHistory", Schema.HaSpMan)); + { + if (!optionsBuilder.IsConfigured) + { + optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable("__EFMigrationsHistory", Schema.HaSpMan)); + } + + } protected override void OnModelCreating(ModelBuilder builder) { From bbd7fb48505262b9ca1cdb525aed21f224bb655a Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 17:28:59 +0200 Subject: [PATCH 05/26] test: Adds unit tests --- .../When_closing_a_financial_year.cs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Commands.Test/FinancialYear/When_closing_a_financial_year.cs diff --git a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs new file mode 100644 index 00000000..d797964e --- /dev/null +++ b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Commands.Handlers.FinancialYear.AddFinancialYear; +using Commands.Handlers.FinancialYear.CloseFinancialYear; + +using Domain; + +using FluentAssertions; + +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 _financialYearRepositoryMock; + private readonly HaSpManContext _haspmanDbContext; + + public When_adding_a_financial_year() + { + _financialYearRepositoryMock = new Mock(); + _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() + + .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _haspmanDbContext); + } + + public AddFinancialYearHandler SUT { get; set; } + + [Fact] + public async Task It_should_add_a_new_financial_year() + { + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var endDate = DateOnly.FromDateTime(DateTime.Now.AddYears(1).AddDays(-1)); + _financialYearRepositoryMock.Setup(x => x.Add(It.Is(year => year.StartDate == startDate))).Verifiable(); + + await SUT.Handle(new AddFinancialYearCommand(startDate), CancellationToken.None); + + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(year => year.StartDate == startDate))); + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(year => year.EndDate == endDate))); + } +} + +public class When_closing_a_financial_year +{ + private readonly Mock _financialYearRepositoryMock; + + public When_closing_a_financial_year() + { + _financialYearRepositoryMock = new Mock(); + SUT = new CloseFinancialYearHandler(_financialYearRepositoryMock.Object); + } + + public CloseFinancialYearHandler SUT { get; set; } + + [Fact] + public async Task It_should_mark_the_year_as_closed() + { + var financialYear = new Domain.FinancialYear(DateOnly.FromDateTime(DateTime.Now), + new List()); + _financialYearRepositoryMock + .Setup(x => x.GetById(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 financialYear = new Domain.FinancialYear(DateOnly.FromDateTime(DateTime.Now), + new List() + { + new CreditTransaction("Random counter party", Guid.NewGuid(), 20,DateTimeOffset.Now, "A description", new List(), null, new List()) + }); + _financialYearRepositoryMock + .Setup(x => x.GetById(financialYear.Id, CancellationToken.None)) + .ReturnsAsync(financialYear); + ; + await SUT.Handle(new CloseFinancialYearCommand(financialYear.Id), CancellationToken.None); + + financialYear.IsClosed.Should().BeTrue(); + financialYear.Transactions.Should().OnlyContain(x => x.Locked); + } + + [Fact] + public async Task It_should_throw_an_exception_when_no_year_is_found() + { + var newGuid = Guid.NewGuid(); + var exception = await Assert.ThrowsAsync(async () => + { + await SUT.Handle(new CloseFinancialYearCommand(newGuid), CancellationToken.None); + }); + Assert.Equal($"No financial year found by Id {newGuid} (Parameter 'Id')", exception.Message); + } +} \ No newline at end of file From 679182adf92b2564be39a73b34c212e5b6727cc8 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 17:29:27 +0200 Subject: [PATCH 06/26] chore: move test to own file --- .../When_closing_a_financial_year.cs | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs index d797964e..c7a78cff 100644 --- a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs @@ -4,59 +4,20 @@ using System.Text; using System.Threading.Tasks; -using Commands.Handlers.FinancialYear.AddFinancialYear; using Commands.Handlers.FinancialYear.CloseFinancialYear; using Domain; using FluentAssertions; -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 _financialYearRepositoryMock; - private readonly HaSpManContext _haspmanDbContext; - - public When_adding_a_financial_year() - { - _financialYearRepositoryMock = new Mock(); - _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() - - .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); - SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _haspmanDbContext); - } - - public AddFinancialYearHandler SUT { get; set; } - - [Fact] - public async Task It_should_add_a_new_financial_year() - { - - var startDate = DateOnly.FromDateTime(DateTime.Now); - var endDate = DateOnly.FromDateTime(DateTime.Now.AddYears(1).AddDays(-1)); - _financialYearRepositoryMock.Setup(x => x.Add(It.Is(year => year.StartDate == startDate))).Verifiable(); - - await SUT.Handle(new AddFinancialYearCommand(startDate), CancellationToken.None); - - _financialYearRepositoryMock - .Verify(x => - x.Add(It.Is(year => year.StartDate == startDate))); - _financialYearRepositoryMock - .Verify(x => - x.Add(It.Is(year => year.EndDate == endDate))); - } -} - public class When_closing_a_financial_year { private readonly Mock _financialYearRepositoryMock; From 6dfec1c167be1f567e33d3747ee4a9d01ffaa320 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 17:29:33 +0200 Subject: [PATCH 07/26] chore: move test to own file --- .../When_adding_a_financial_year.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Commands.Test/FinancialYear/When_adding_a_financial_year.cs diff --git a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs new file mode 100644 index 00000000..bd91547e --- /dev/null +++ b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs @@ -0,0 +1,47 @@ +using Commands.Handlers.FinancialYear.AddFinancialYear; + +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 _financialYearRepositoryMock; + private readonly HaSpManContext _haspmanDbContext; + + public When_adding_a_financial_year() + { + _financialYearRepositoryMock = new Mock(); + _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() + + .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _haspmanDbContext); + } + + public AddFinancialYearHandler SUT { get; set; } + + [Fact] + public async Task It_should_add_a_new_financial_year() + { + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var endDate = DateOnly.FromDateTime(DateTime.Now.AddYears(1).AddDays(-1)); + _financialYearRepositoryMock.Setup(x => x.Add(It.Is(year => year.StartDate == startDate))).Verifiable(); + + await SUT.Handle(new AddFinancialYearCommand(startDate), CancellationToken.None); + + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(year => year.StartDate == startDate))); + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(year => year.EndDate == endDate))); + } +} \ No newline at end of file From e975238e1e74717e2739aecbf2d68359dd8c4a67 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 17:47:37 +0200 Subject: [PATCH 08/26] test: adds unit tests for editing locked transactions --- .../When_closing_a_financial_year.cs | 4 +- .../Transaction/When_editing_a_transaction.cs | 55 +++++++++++++++++++ Domain/Transaction.cs | 45 +++++++-------- 3 files changed, 77 insertions(+), 27 deletions(-) create mode 100644 Commands.Test/Transaction/When_editing_a_transaction.cs diff --git a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs index c7a78cff..831f7c0c 100644 --- a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs @@ -34,7 +34,7 @@ public When_closing_a_financial_year() public async Task It_should_mark_the_year_as_closed() { var financialYear = new Domain.FinancialYear(DateOnly.FromDateTime(DateTime.Now), - new List()); + new List()); _financialYearRepositoryMock .Setup(x => x.GetById(financialYear.Id, CancellationToken.None)) .ReturnsAsync(financialYear); @@ -47,7 +47,7 @@ public async Task It_should_mark_the_year_as_closed() public async Task It_should_mark_all_related_transactions_as_as_locked() { var financialYear = new Domain.FinancialYear(DateOnly.FromDateTime(DateTime.Now), - new List() + new List() { new CreditTransaction("Random counter party", Guid.NewGuid(), 20,DateTimeOffset.Now, "A description", new List(), null, new List()) }); diff --git a/Commands.Test/Transaction/When_editing_a_transaction.cs b/Commands.Test/Transaction/When_editing_a_transaction.cs new file mode 100644 index 00000000..887d9af1 --- /dev/null +++ b/Commands.Test/Transaction/When_editing_a_transaction.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Commands.Handlers.Transaction.EditTransaction; + +using Domain; + +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 _transactionRepositoryMock; + + public When_editing_a_transaction() + { + _transactionRepositoryMock = new Mock(); + var mediatorMock = new Mock(); + SUT = new EditTransactionHandler(_transactionRepositoryMock.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(), null, new List()); + transaction.Lock(); + _transactionRepositoryMock + .Setup(x => x.GetByIdAsync(transaction.Id, CancellationToken.None)) + .ReturnsAsync(transaction); + + var exception = await Assert.ThrowsAsync(async () => await SUT.Handle( + new EditTransactionCommand(transaction.Id, "Random counter party name", null, bankAccountId, + DateTimeOffset.Now, "Another description", new List(), + new List()), CancellationToken.None)); + + exception.Message.Should().Be("Transaction is locked"); + } +} diff --git a/Domain/Transaction.cs b/Domain/Transaction.cs index 6c55409d..34050a9c 100644 --- a/Domain/Transaction.cs +++ b/Domain/Transaction.cs @@ -61,10 +61,8 @@ protected Transaction( public void ChangeCounterParty(string counterPartyName, Guid? memberId) { - if (Locked) - { - throw new Exception("Transaction is locked"); - } + + AssertTransactionIsNotLocked(); if (memberId == MemberId && counterPartyName == CounterPartyName) { return; @@ -76,10 +74,8 @@ public void ChangeCounterParty(string counterPartyName, Guid? memberId) public void ChangeBankAccountId(Guid bankAccountId) { - if (Locked) - { - throw new Exception("Transaction is locked"); - } + + AssertTransactionIsNotLocked(); if (bankAccountId == BankAccountId) { return; @@ -90,10 +86,8 @@ public void ChangeBankAccountId(Guid bankAccountId) public void ChangeReceivedDateTime(DateTimeOffset receivedDateTime) { - if (Locked) - { - throw new Exception("Transaction is locked"); - } + + AssertTransactionIsNotLocked(); if (receivedDateTime > DateTimeOffset.Now) { throw new ArgumentException($"Received date is set to be in the future: {receivedDateTime}", @@ -110,10 +104,8 @@ public void ChangeReceivedDateTime(DateTimeOffset receivedDateTime) public void ChangeAmount(decimal amount, ICollection transactionTypeAmounts) { - if (Locked) - { - throw new Exception("Transaction is locked"); - } + + AssertTransactionIsNotLocked(); var sumOfTransactionTypeAmounts = transactionTypeAmounts.Sum(x => x.Amount); if (amount != sumOfTransactionTypeAmounts) { @@ -132,10 +124,8 @@ public void ChangeAmount(decimal amount, ICollection tran public void ChangeDescription(string description) { - if (Locked) - { - throw new Exception("Transaction is locked"); - } + + AssertTransactionIsNotLocked(); if (description == Description) { return; @@ -146,22 +136,27 @@ public void ChangeDescription(string description) public void AddAttachments(ICollection attachments) { - if (Locked) - { - throw new Exception("Transaction is locked"); - } + AssertTransactionIsNotLocked(); foreach (var attachment in attachments) { Attachments.Add(attachment); } } + private void AssertTransactionIsNotLocked() + { + if (Locked) + { + throw new InvalidOperationException("Transaction is locked"); + } + } + public void Lock() { Locked = true; } - public bool Locked { get; set; } + public bool Locked { get; private set; } } public class DebitTransaction : Transaction { From d6c598e89ccb95b63d3c8d6e4a6bed578de457ab Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 24 Jun 2023 23:56:51 +0200 Subject: [PATCH 09/26] Revert "chore: remove repository interfaces from Domain project" This reverts commit 686d8d824e712752bf499fb27af599154d8a0437. # Conflicts: # Domain/Transaction.cs --- .../AddBankAccount/AddBankAccountHandler.cs | 2 +- .../EditBankAccount/EditBankAccountHandler.cs | 5 ++--- .../Handlers/Member/AddMember/AddMemberHandler.cs | 3 ++- .../Member/DeleteMember/DeleteMemberCommand.cs | 2 +- .../Handlers/Member/EditMember/EditMemberHandler.cs | 3 ++- .../ExtendMembership/ExtendMembershipHandler.cs | 2 +- .../AddAttachments/AddAttachmentsCommand.cs | 3 ++- .../Handlers/Transaction/TransactionTypeValidator.cs | 2 ++ Domain/Interfaces/IAttachmentStorage.cs | 11 +++++++++++ Domain/Interfaces/IBankAccountRepository.cs | 10 ++++++++++ Domain/Interfaces/IMemberRepository.cs | 11 +++++++++++ Persistence/HaSpManContext.cs | 3 ++- Persistence/Repositories/AttachmentStorage.cs | 10 ++-------- Persistence/Repositories/BankAccountRepository.cs | 10 +--------- Persistence/Repositories/MemberRepository.cs | 11 +---------- .../GetAttachment/GetAttachmentCommand.cs | 6 ++++-- Web/Pages/Transactions/TransactionForm.razor | 2 +- Web/Startup.cs | 2 ++ 18 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 Domain/Interfaces/IAttachmentStorage.cs create mode 100644 Domain/Interfaces/IBankAccountRepository.cs create mode 100644 Domain/Interfaces/IMemberRepository.cs diff --git a/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs b/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs index c15fcb34..7dabc9ee 100644 --- a/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs +++ b/Commands/Handlers/BankAccount/AddBankAccount/AddBankAccountHandler.cs @@ -1,7 +1,7 @@ using Commands.Extensions; using Commands.Services; -using Persistence.Repositories; +using Domain.Interfaces; namespace Commands.Handlers.BankAccount.AddBankAccount; diff --git a/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs b/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs index b643b298..a2366733 100644 --- a/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs +++ b/Commands/Handlers/EditBankAccount/EditBankAccountHandler.cs @@ -1,10 +1,9 @@ using Commands.Extensions; -using Commands.Handlers.BankAccount.EditBankAccount; using Commands.Services; -using Persistence.Repositories; +using Domain.Interfaces; -namespace Commands.Handlers.EditBankAccount; +namespace Commands.Handlers.BankAccount.EditBankAccount; public class EditBankAccountHandler : IRequestHandler { diff --git a/Commands/Handlers/Member/AddMember/AddMemberHandler.cs b/Commands/Handlers/Member/AddMember/AddMemberHandler.cs index 08192d96..5e85dfb3 100644 --- a/Commands/Handlers/Member/AddMember/AddMemberHandler.cs +++ b/Commands/Handlers/Member/AddMember/AddMemberHandler.cs @@ -1,10 +1,11 @@ using Commands.Extensions; using Commands.Services; +using Domain.Interfaces; + using Microsoft.EntityFrameworkCore; using Persistence; -using Persistence.Repositories; namespace Commands.Handlers.Member.AddMember; diff --git a/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs b/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs index cc8f836c..1458b4fc 100644 --- a/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs +++ b/Commands/Handlers/Member/DeleteMember/DeleteMemberCommand.cs @@ -1,4 +1,4 @@ -using Persistence.Repositories; +using Domain.Interfaces; namespace Commands.Handlers.Member.DeleteMember; diff --git a/Commands/Handlers/Member/EditMember/EditMemberHandler.cs b/Commands/Handlers/Member/EditMember/EditMemberHandler.cs index 71a9f153..11125334 100644 --- a/Commands/Handlers/Member/EditMember/EditMemberHandler.cs +++ b/Commands/Handlers/Member/EditMember/EditMemberHandler.cs @@ -1,10 +1,11 @@ using Commands.Extensions; using Commands.Services; +using Domain.Interfaces; + using Microsoft.EntityFrameworkCore; using Persistence; -using Persistence.Repositories; namespace Commands.Handlers.Member.EditMember; diff --git a/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs b/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs index bd9e0d6d..5ae4f804 100644 --- a/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs +++ b/Commands/Handlers/Member/ExtendMembership/ExtendMembershipHandler.cs @@ -1,7 +1,7 @@ using Commands.Extensions; using Commands.Services; -using Persistence.Repositories; +using Domain.Interfaces; namespace Commands.Handlers.Member.ExtendMembership; diff --git a/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs b/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs index 0dfaeb22..621e91d5 100644 --- a/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs +++ b/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs @@ -1,4 +1,5 @@ using Domain; +using Domain.Interfaces; using FluentValidation; @@ -42,7 +43,7 @@ public async Task Handle(AddAttachmentsCommand request, CancellationToken { var transaction = await _transactionRepository.GetByIdAsync(request.TransactionId, cancellationToken) ?? throw new ArgumentException($"No transaction found for Id {request.TransactionId}", nameof(request.TransactionId)); - + var transactionAttachments = await StoreAttachmentsAsync(request, cancellationToken); transaction.AddAttachments(transactionAttachments); diff --git a/Commands/Handlers/Transaction/TransactionTypeValidator.cs b/Commands/Handlers/Transaction/TransactionTypeValidator.cs index 93608d8b..c602164c 100644 --- a/Commands/Handlers/Transaction/TransactionTypeValidator.cs +++ b/Commands/Handlers/Transaction/TransactionTypeValidator.cs @@ -2,6 +2,8 @@ using FluentValidation; +using Types; + namespace Commands.Handlers.Transaction; public class TransactionTypeValidator : AbstractValidator diff --git a/Domain/Interfaces/IAttachmentStorage.cs b/Domain/Interfaces/IAttachmentStorage.cs new file mode 100644 index 00000000..f18d0388 --- /dev/null +++ b/Domain/Interfaces/IAttachmentStorage.cs @@ -0,0 +1,11 @@ +using Types; + +namespace Domain.Interfaces; + +public interface IAttachmentStorage +{ + Task AddAsync(string path, string contentType, byte[] bytes, CancellationToken cancellationToken); + + Task GetAsync(string blobUri, CancellationToken cancellationToken); + void Delete(string blobUri, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Domain/Interfaces/IBankAccountRepository.cs b/Domain/Interfaces/IBankAccountRepository.cs new file mode 100644 index 00000000..f79308e1 --- /dev/null +++ b/Domain/Interfaces/IBankAccountRepository.cs @@ -0,0 +1,10 @@ +namespace Domain.Interfaces; + +public interface IBankAccountRepository +{ + Task GetByIdAsync(Guid id, CancellationToken ct); + Task> GetAllAsync(CancellationToken ct); + void Add(BankAccount account); + void Remove(BankAccount account); + Task SaveAsync(CancellationToken ct); +} diff --git a/Domain/Interfaces/IMemberRepository.cs b/Domain/Interfaces/IMemberRepository.cs new file mode 100644 index 00000000..202951f5 --- /dev/null +++ b/Domain/Interfaces/IMemberRepository.cs @@ -0,0 +1,11 @@ +namespace Domain.Interfaces; + +public interface IMemberRepository +{ + Task GetById(Guid id); + Task GetByEmail(string email); + Task> GetAllAsync(); + void Add(Member member); + void Remove(Member member); + Task Save(CancellationToken cancellationToken); +} diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index e2238c87..1f91246f 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -22,7 +22,8 @@ public HaSpManContext(DbContextOptions options) public DbSet BankAccounts { get; set; } = null!; public DbSet BankAccountsWithTotals { get; set; } = null!; public DbSet Transactions { get; set; } = null!; - public DbSet FinancialYears { get; set; } = null!; + + public DbSet FinancialYears { get;set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/Persistence/Repositories/AttachmentStorage.cs b/Persistence/Repositories/AttachmentStorage.cs index 2f05a499..a79cf639 100644 --- a/Persistence/Repositories/AttachmentStorage.cs +++ b/Persistence/Repositories/AttachmentStorage.cs @@ -2,6 +2,8 @@ using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; +using Domain.Interfaces; + using Microsoft.Extensions.Options; using Persistence.Configuration; @@ -48,11 +50,3 @@ public void Delete(string blobUri, CancellationToken cancellationToken) throw new NotImplementedException(); } } - -public interface IAttachmentStorage -{ - Task AddAsync(string path, string contentType, byte[] bytes, CancellationToken cancellationToken); - - Task GetAsync(string blobUri, CancellationToken cancellationToken); - void Delete(string blobUri, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/Persistence/Repositories/BankAccountRepository.cs b/Persistence/Repositories/BankAccountRepository.cs index 31c00e62..45d37cf1 100644 --- a/Persistence/Repositories/BankAccountRepository.cs +++ b/Persistence/Repositories/BankAccountRepository.cs @@ -1,4 +1,5 @@ using Domain; +using Domain.Interfaces; using Microsoft.EntityFrameworkCore; @@ -40,12 +41,3 @@ public async Task SaveAsync(CancellationToken ct) await _context.SaveChangesAsync(ct); } } - -public interface IBankAccountRepository -{ - Task GetByIdAsync(Guid id, CancellationToken ct); - Task> GetAllAsync(CancellationToken ct); - void Add(BankAccount account); - void Remove(BankAccount account); - Task SaveAsync(CancellationToken ct); -} diff --git a/Persistence/Repositories/MemberRepository.cs b/Persistence/Repositories/MemberRepository.cs index 2eefecc0..0cce1b75 100644 --- a/Persistence/Repositories/MemberRepository.cs +++ b/Persistence/Repositories/MemberRepository.cs @@ -1,4 +1,5 @@ using Domain; +using Domain.Interfaces; using Microsoft.EntityFrameworkCore; @@ -44,13 +45,3 @@ public async Task Save(CancellationToken cancellationToken) } } - -public interface IMemberRepository -{ - Task GetById(Guid id); - Task GetByEmail(string email); - Task> GetAllAsync(); - void Add(Member member); - void Remove(Member member); - Task Save(CancellationToken cancellationToken); -} diff --git a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs index 0a4cb773..d64e5525 100644 --- a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs +++ b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs @@ -1,8 +1,10 @@ -using Persistence.Repositories; +using Domain.Interfaces; + +using Persistence.Repositories; using Types; -namespace Queries.Transactions.GetAttachment; +namespace Commands.Handlers.Transaction.GetAttachment; public record GetAttachmentQuery(Guid TransactionId, string FileName) : IRequest; diff --git a/Web/Pages/Transactions/TransactionForm.razor b/Web/Pages/Transactions/TransactionForm.razor index 8de63217..f4fd3b03 100644 --- a/Web/Pages/Transactions/TransactionForm.razor +++ b/Web/Pages/Transactions/TransactionForm.razor @@ -4,7 +4,7 @@ @using Queries.Members.Handlers.GetMemberById @using Queries.Members.ViewModels @using Queries.Members.Handlers.AutocompleteMember -@using Queries.Transactions.GetAttachment +@using Commands.Handlers.Transaction.GetAttachment @inject IWebHostEnvironment Environment @inject IDialogService dialogService diff --git a/Web/Startup.cs b/Web/Startup.cs index d50321d4..fda45774 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -1,6 +1,8 @@ using Commands.Handlers.Transaction.AddDebitTransaction; using Commands.Services; +using Domain.Interfaces; + using FluentValidation; using MediatR; From c9b55aead9cf52de554b3d2ad77287a6b2e713ff Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sun, 25 Jun 2023 16:37:55 +0200 Subject: [PATCH 10/26] chore: move some files around --- .../FinancialYear/When_adding_a_financial_year.cs | 2 ++ .../FinancialYear/When_closing_a_financial_year.cs | 1 + .../AddFinancialYear/AddFinancialYearHandler.cs | 4 +++- .../CloseFinancialYear/CloseFinancialYearHandler.cs | 4 +++- Domain/Interfaces/IFinancialYearRepository.cs | 8 ++++++++ Persistence/Repositories/FinancialYearRepository.cs | 8 +------- 6 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 Domain/Interfaces/IFinancialYearRepository.cs diff --git a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs index bd91547e..4483cdb1 100644 --- a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs @@ -1,5 +1,7 @@ using Commands.Handlers.FinancialYear.AddFinancialYear; +using Domain.Interfaces; + using Microsoft.EntityFrameworkCore; using Moq; diff --git a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs index 831f7c0c..b3c5c382 100644 --- a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs @@ -7,6 +7,7 @@ using Commands.Handlers.FinancialYear.CloseFinancialYear; using Domain; +using Domain.Interfaces; using FluentAssertions; diff --git a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs index 82bd4e10..9f24cd5c 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs @@ -1,4 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using Domain.Interfaces; + +using Microsoft.EntityFrameworkCore; using Persistence; using Persistence.Repositories; diff --git a/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs index c7fab9a8..44455e02 100644 --- a/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs @@ -1,4 +1,6 @@ -using Persistence.Repositories; +using Domain.Interfaces; + +using Persistence.Repositories; namespace Commands.Handlers.FinancialYear.CloseFinancialYear; diff --git a/Domain/Interfaces/IFinancialYearRepository.cs b/Domain/Interfaces/IFinancialYearRepository.cs new file mode 100644 index 00000000..10731eeb --- /dev/null +++ b/Domain/Interfaces/IFinancialYearRepository.cs @@ -0,0 +1,8 @@ +namespace Domain.Interfaces; + +public interface IFinancialYearRepository +{ + Task GetById(Guid id, CancellationToken cancellationToken); + void Add(FinancialYear financialYear); + Task SaveChangesAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs index 9e05ea1f..0795e7ff 100644 --- a/Persistence/Repositories/FinancialYearRepository.cs +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -1,4 +1,5 @@ using Domain; +using Domain.Interfaces; using Microsoft.EntityFrameworkCore; @@ -29,11 +30,4 @@ public async Task SaveChangesAsync(CancellationToken cancellationToken) { await _context.SaveChangesAsync(cancellationToken); } -} - -public interface IFinancialYearRepository -{ - Task GetById(Guid id, CancellationToken cancellationToken); - void Add(FinancialYear financialYear); - Task SaveChangesAsync(CancellationToken cancellationToken); } \ No newline at end of file From 78a35d06582df516f2aeb40f6a40b6f081a75d32 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sun, 25 Jun 2023 17:56:15 +0200 Subject: [PATCH 11/26] feat: Adds FinancialYearConfiguration --- .../When_adding_a_financial_year.cs | 88 ++++++++++++++++--- .../When_closing_a_financial_year.cs | 12 +-- .../AddFinancialYearCommand.cs | 3 +- .../AddFinancialYearHandler.cs | 38 ++++---- .../CloseFinancialYearHandler.cs | 2 +- .../SetFinancialYearConfigurationCommand.cs | 20 +++++ .../SetFinancialYearConfigurationHandler.cs | 18 ++++ Domain/FinancialYear.cs | 10 +-- Domain/FinancialYearConfiguration.cs | 20 +++++ Domain/Interfaces/IFinancialYearRepository.cs | 10 ++- ...FinancialYearConfigurationConfiguration.cs | 14 +++ Persistence/HaSpManContext.cs | 2 + .../FinancialYearConfigurationRepository.cs | 30 +++++++ .../Repositories/FinancialYearRepository.cs | 7 +- 14 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs create mode 100644 Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs create mode 100644 Domain/FinancialYearConfiguration.cs create mode 100644 Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs create mode 100644 Persistence/Repositories/FinancialYearConfigurationRepository.cs diff --git a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs index 4483cdb1..afe1d0e5 100644 --- a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs @@ -1,5 +1,6 @@ using Commands.Handlers.FinancialYear.AddFinancialYear; +using Domain; using Domain.Interfaces; using Microsoft.EntityFrameworkCore; @@ -17,33 +18,92 @@ public class When_adding_a_financial_year { private readonly Mock _financialYearRepositoryMock; private readonly HaSpManContext _haspmanDbContext; + private readonly Mock _financialYearConfigurationMock; public When_adding_a_financial_year() { _financialYearRepositoryMock = new Mock(); + _financialYearConfigurationMock = new Mock(); _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() - + .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); - SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _haspmanDbContext); + SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _financialYearConfigurationMock.Object, + _haspmanDbContext); } public AddFinancialYearHandler SUT { get; set; } - [Fact] - public async Task It_should_add_a_new_financial_year() + + + + 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(financialYear => financialYear.StartDate == startDate))) + .Verifiable(); + _financialYearRepositoryMock + .Setup(x => + x.Add(It.Is(financialYear => financialYear.EndDate == endDate))) + .Verifiable(); + + _financialYearConfigurationMock.Setup(x => x.Get(It.IsAny())) + .ReturnsAsync(new FinancialYearConfiguration(new DateTimeOffset(new DateTime(2021, 9, 1)))); + + await SUT.Handle(new AddFinancialYearCommand(), CancellationToken.None); + + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(financialYear => financialYear.StartDate == startDate))); + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(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())) + .ReturnsAsync(new Domain.FinancialYear(new DateTimeOffset(new DateTime(2022, 9, 1)), + new DateTimeOffset(new DateTime(2023,8,31)), new List())); + + _financialYearRepositoryMock + .Setup(x => + x.Add(It.Is(financialYear => financialYear.StartDate == startDate))) + .Verifiable(); + _financialYearRepositoryMock + .Setup(x => + x.Add(It.Is(financialYear => financialYear.EndDate == endDate))) + .Verifiable(); + + _financialYearConfigurationMock.Setup(x => x.Get(It.IsAny())) + .ReturnsAsync(new FinancialYearConfiguration(new DateTimeOffset(new DateTime(2021, 9, 1)))); - var startDate = DateOnly.FromDateTime(DateTime.Now); - var endDate = DateOnly.FromDateTime(DateTime.Now.AddYears(1).AddDays(-1)); - _financialYearRepositoryMock.Setup(x => x.Add(It.Is(year => year.StartDate == startDate))).Verifiable(); + await SUT.Handle(new AddFinancialYearCommand(), CancellationToken.None); - await SUT.Handle(new AddFinancialYearCommand(startDate), CancellationToken.None); + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(financialYear => financialYear.StartDate == startDate))); + _financialYearRepositoryMock + .Verify(x => + x.Add(It.Is(financialYear => financialYear.EndDate == endDate))); - _financialYearRepositoryMock - .Verify(x => - x.Add(It.Is(year => year.StartDate == startDate))); - _financialYearRepositoryMock - .Verify(x => - x.Add(It.Is(year => year.EndDate == endDate))); + } + } } } \ No newline at end of file diff --git a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs index b3c5c382..8d9111a7 100644 --- a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs @@ -34,10 +34,10 @@ public When_closing_a_financial_year() [Fact] public async Task It_should_mark_the_year_as_closed() { - var financialYear = new Domain.FinancialYear(DateOnly.FromDateTime(DateTime.Now), - new List()); + var startDate = DateTimeOffset.Now; + var financialYear = new Domain.FinancialYear(startDate, startDate.AddYears(1).AddDays(-1), new List()); _financialYearRepositoryMock - .Setup(x => x.GetById(financialYear.Id, CancellationToken.None)) + .Setup(x => x.GetByIdAsync(financialYear.Id, CancellationToken.None)) .ReturnsAsync(financialYear); ; await SUT.Handle(new CloseFinancialYearCommand(financialYear.Id), CancellationToken.None); @@ -47,13 +47,13 @@ public async Task It_should_mark_the_year_as_closed() [Fact] public async Task It_should_mark_all_related_transactions_as_as_locked() { - var financialYear = new Domain.FinancialYear(DateOnly.FromDateTime(DateTime.Now), - new List() + var startDate = DateTimeOffset.Now; + var financialYear = new Domain.FinancialYear(startDate, startDate.AddYears(1).AddDays(-1), new List() { new CreditTransaction("Random counter party", Guid.NewGuid(), 20,DateTimeOffset.Now, "A description", new List(), null, new List()) }); _financialYearRepositoryMock - .Setup(x => x.GetById(financialYear.Id, CancellationToken.None)) + .Setup(x => x.GetByIdAsync(financialYear.Id, CancellationToken.None)) .ReturnsAsync(financialYear); ; await SUT.Handle(new CloseFinancialYearCommand(financialYear.Id), CancellationToken.None); diff --git a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs index 7d858065..2c2ba878 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs @@ -2,13 +2,12 @@ namespace Commands.Handlers.FinancialYear.AddFinancialYear; -public record AddFinancialYearCommand(DateOnly StartDate) : IRequest; +public record AddFinancialYearCommand() : IRequest; public class AddFinancialYearCommandValidator : AbstractValidator { public AddFinancialYearCommandValidator() { - RuleFor(x => x.StartDate).NotEmpty(); } } \ No newline at end of file diff --git a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs index 9f24cd5c..13f5cad2 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs @@ -1,7 +1,5 @@ using Domain.Interfaces; -using Microsoft.EntityFrameworkCore; - using Persistence; using Persistence.Repositories; @@ -10,29 +8,39 @@ namespace Commands.Handlers.FinancialYear.AddFinancialYear; public class AddFinancialYearHandler : IRequestHandler { private readonly IFinancialYearRepository _financialYearRepository; + private readonly IFinancialYearConfigurationRepository _financialYearConfigurationRepository; + private readonly HaSpManContext _haSpManContext; - public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository, HaSpManContext haSpManContext) + public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository, + IFinancialYearConfigurationRepository financialYearConfigurationRepository, + HaSpManContext haSpManContext) { _financialYearRepository = financialYearRepository; + _financialYearConfigurationRepository = financialYearConfigurationRepository; _haSpManContext = haSpManContext; } public async Task Handle(AddFinancialYearCommand request, CancellationToken cancellationToken) { - await EnsureStartDateNotInExistingFinancialYear(request, cancellationToken); - var financialYear = new Domain.FinancialYear(request.StartDate, new List()); + 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()); + _financialYearRepository.Add(financialYear); await _financialYearRepository.SaveChangesAsync(cancellationToken); return financialYear.Id; } - - private async Task EnsureStartDateNotInExistingFinancialYear(AddFinancialYearCommand request, CancellationToken cancellationToken) - { - var financialYearAlreadyInExistingYear = await _haSpManContext.FinancialYears - .AsNoTracking() - .AnyAsync(x => x.EndDate <= request.StartDate, cancellationToken); - - if (financialYearAlreadyInExistingYear) - throw new InvalidOperationException("Start date of year is in already existing year"); - } } \ No newline at end of file diff --git a/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs index 44455e02..e1520721 100644 --- a/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/CloseFinancialYear/CloseFinancialYearHandler.cs @@ -14,7 +14,7 @@ public CloseFinancialYearHandler(IFinancialYearRepository financialYearRepositor } public async Task Handle(CloseFinancialYearCommand request, CancellationToken cancellationToken) { - var financialYear = await _financialYearRepository.GetById(request.Id, 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(); diff --git a/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs b/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs new file mode 100644 index 00000000..2cac9747 --- /dev/null +++ b/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs @@ -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 +{ + public SetFinancialYearConfigurationCommandValidator() + { + RuleFor(x => x.StartDate).NotEmpty(); + } +} \ No newline at end of file diff --git a/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs b/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs new file mode 100644 index 00000000..f1228d1b --- /dev/null +++ b/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs @@ -0,0 +1,18 @@ +using Domain.Interfaces; + +namespace Commands.Handlers.FinancialYearConfiguration; + +public class SetFinancialYearConfigurationHandler : IRequestHandler +{ + private readonly IFinancialYearConfigurationRepository _financialYearConfigurationRepository; + + public SetFinancialYearConfigurationHandler(IFinancialYearConfigurationRepository financialYearConfigurationRepository) + { + _financialYearConfigurationRepository = financialYearConfigurationRepository; + } + public async Task Handle(SetFinancialYearConfigurationCommand request, CancellationToken cancellationToken) + { + _financialYearConfigurationRepository.Set(new Domain.FinancialYearConfiguration(request.StartDate)); + await _financialYearConfigurationRepository.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/Domain/FinancialYear.cs b/Domain/FinancialYear.cs index 7ae01299..36510412 100644 --- a/Domain/FinancialYear.cs +++ b/Domain/FinancialYear.cs @@ -5,18 +5,18 @@ public class FinancialYear #pragma warning disable CS8618 public FinancialYear(){ } // Make EFCore happy #pragma warning restore CS8618 - public FinancialYear(DateOnly startDate, ICollection transactions) + public FinancialYear(DateTimeOffset startDate, DateTimeOffset endDate, ICollection transactions) { StartDate = startDate; - EndDate = startDate.AddYears(1).AddDays(-1); + EndDate = endDate; Transactions = transactions; } public Guid Id { get; private set; } - public DateOnly StartDate { get; set; } - public DateOnly EndDate { get; private set;} + public DateTimeOffset StartDate { get; set; } + public DateTimeOffset EndDate { get; private set;} - public bool IsClosed { get; set; } + public bool IsClosed { get; private set; } public ICollection Transactions { get; set; } diff --git a/Domain/FinancialYearConfiguration.cs b/Domain/FinancialYearConfiguration.cs new file mode 100644 index 00000000..e05f6f82 --- /dev/null +++ b/Domain/FinancialYearConfiguration.cs @@ -0,0 +1,20 @@ +namespace Domain; + +public class FinancialYearConfiguration +{ +#pragma warning disable CS8618 + public FinancialYearConfiguration() + { + + }// Make EFCore happy +#pragma warning restore CS8618 + + public FinancialYearConfiguration(DateTimeOffset startDate) + { + StartDate = startDate; + } + public Guid Id { get; set; } + public DateTimeOffset StartDate { get; private set; } + + public TimeSpan Duration = TimeSpan.FromDays(365); +} \ No newline at end of file diff --git a/Domain/Interfaces/IFinancialYearRepository.cs b/Domain/Interfaces/IFinancialYearRepository.cs index 10731eeb..0bdb2ac4 100644 --- a/Domain/Interfaces/IFinancialYearRepository.cs +++ b/Domain/Interfaces/IFinancialYearRepository.cs @@ -1,8 +1,16 @@ namespace Domain.Interfaces; +public interface IFinancialYearConfigurationRepository +{ + Task Get(CancellationToken cancellationToken); + void Set(FinancialYearConfiguration financialYearConfiguration); + Task SaveChangesAsync(CancellationToken cancellationToken); +} + public interface IFinancialYearRepository { - Task GetById(Guid id, CancellationToken cancellationToken); + Task GetByIdAsync(Guid id, CancellationToken cancellationToken); void Add(FinancialYear financialYear); Task SaveChangesAsync(CancellationToken cancellationToken); + Task GetMostRecentAsync(CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs new file mode 100644 index 00000000..dbedfcab --- /dev/null +++ b/Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs @@ -0,0 +1,14 @@ +using Domain; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntityConfigurations; + +public class FinancialYearConfigurationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(x => x.StartDate).IsRequired(); + } +} \ No newline at end of file diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index 1f91246f..ae31e53c 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -25,6 +25,8 @@ public HaSpManContext(DbContextOptions options) public DbSet FinancialYears { get;set; } = null!; + public DbSet FinancialYearConfigurations { get; set; } = null!; + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) diff --git a/Persistence/Repositories/FinancialYearConfigurationRepository.cs b/Persistence/Repositories/FinancialYearConfigurationRepository.cs new file mode 100644 index 00000000..d475aa3b --- /dev/null +++ b/Persistence/Repositories/FinancialYearConfigurationRepository.cs @@ -0,0 +1,30 @@ +using Domain; +using Domain.Interfaces; + +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Repositories; + +public class FinancialYearConfigurationRepository : IFinancialYearConfigurationRepository +{ + private readonly HaSpManContext _dbContext; + + public FinancialYearConfigurationRepository(HaSpManContext dbContext) + { + _dbContext = dbContext; + } + public Task Get(CancellationToken cancellationToken) + { + return _dbContext.FinancialYearConfigurations.SingleOrDefaultAsync(cancellationToken); + } + + public void Set(FinancialYearConfiguration financialYearConfiguration) + { + _dbContext.FinancialYearConfigurations.Update(financialYearConfiguration); + } + + public async Task SaveChangesAsync(CancellationToken cancellationToken) + { + await _dbContext.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs index 0795e7ff..1404ed1b 100644 --- a/Persistence/Repositories/FinancialYearRepository.cs +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -16,7 +16,7 @@ public FinancialYearRepository(IDbContextFactory contextFactory) _context = contextFactory.CreateDbContext(); } - public Task GetById(Guid id, CancellationToken cancellationToken) + public Task GetByIdAsync(Guid id, CancellationToken cancellationToken) { return _context.FinancialYears.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); } @@ -30,4 +30,9 @@ public async Task SaveChangesAsync(CancellationToken cancellationToken) { await _context.SaveChangesAsync(cancellationToken); } + + public Task GetMostRecentAsync(CancellationToken cancellationToken) + { + return _context.FinancialYears.OrderByDescending(x => x.StartDate).FirstOrDefaultAsync(cancellationToken); + } } \ No newline at end of file From 321cbabbe5fd80970239a3b90e74ee9ed28778e5 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sun, 25 Jun 2023 18:57:57 +0200 Subject: [PATCH 12/26] feat: Moves transactions under FinancialYear domain --- .../Transaction/When_editing_a_transaction.cs | 23 ++-- .../AddAttachments/AddAttachmentsCommand.cs | 16 +-- .../AddCreditTransactionHandler.cs | 18 ++- .../AddDebitTransactionHandler.cs | 18 +-- .../EditTransaction/EditTransactionHandler.cs | 50 ++++---- .../EditTransaction/EditTransationCommand.cs | 2 +- Domain/FinancialYear.cs | 28 ++++- Domain/Interfaces/IFinancialYearRepository.cs | 3 + .../FinancialYearEntityTypeConfiguration.cs | 17 +++ Persistence/HaSpManContext.cs | 5 +- .../Repositories/FinancialYearRepository.cs | 19 +++ .../Repositories/TransactionRepository.cs | 113 +++++++++--------- .../AutocompleteCounterpartyHandler.cs | 1 + .../GetAttachment/GetAttachmentCommand.cs | 19 +-- .../Handlers/GetTransactionByIdHandler.cs | 2 +- .../Handlers/GetTransactionsHandler.cs | 2 +- Web/Pages/Transactions/EditTransaction.razor | 2 +- Web/Pages/Transactions/TransactionForm.razor | 2 +- Web/Startup.cs | 1 - 19 files changed, 218 insertions(+), 123 deletions(-) create mode 100644 Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs diff --git a/Commands.Test/Transaction/When_editing_a_transaction.cs b/Commands.Test/Transaction/When_editing_a_transaction.cs index 887d9af1..15d3ea89 100644 --- a/Commands.Test/Transaction/When_editing_a_transaction.cs +++ b/Commands.Test/Transaction/When_editing_a_transaction.cs @@ -7,6 +7,7 @@ using Commands.Handlers.Transaction.EditTransaction; using Domain; +using Domain.Interfaces; using FluentAssertions; @@ -23,13 +24,13 @@ namespace Commands.Test.Transaction; public class When_editing_a_transaction { - private readonly Mock _transactionRepositoryMock; + private readonly Mock _financialYearRepositoryMock; public When_editing_a_transaction() { - _transactionRepositoryMock = new Mock(); + _financialYearRepositoryMock = new Mock(); var mediatorMock = new Mock(); - SUT = new EditTransactionHandler(_transactionRepositoryMock.Object, mediatorMock.Object); + SUT = new EditTransactionHandler(_financialYearRepositoryMock.Object, mediatorMock.Object); } public EditTransactionHandler SUT { get; set; } @@ -38,18 +39,24 @@ public When_editing_a_transaction() 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(), null, new List()); - transaction.Lock(); - _transactionRepositoryMock - .Setup(x => x.GetByIdAsync(transaction.Id, CancellationToken.None)) - .ReturnsAsync(transaction); + var financialYear = new Domain.FinancialYear(new DateTimeOffset(new DateTime(2022,9,1)), new DateTimeOffset(new DateTime(2023,8,31)), new List() + { + transaction + }); + financialYear.Close(); + + _financialYearRepositoryMock + .Setup(x => x.GetFinancialYearByTransactionId(transaction.Id, CancellationToken.None)) + .ReturnsAsync(financialYear); var exception = await Assert.ThrowsAsync(async () => await SUT.Handle( new EditTransactionCommand(transaction.Id, "Random counter party name", null, bankAccountId, DateTimeOffset.Now, "Another description", new List(), new List()), CancellationToken.None)); - exception.Message.Should().Be("Transaction is locked"); + exception.Message.Should().Be("Financial year is closed"); } } diff --git a/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs b/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs index 621e91d5..4fc948dd 100644 --- a/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs +++ b/Commands/Handlers/Transaction/AddAttachments/AddAttachmentsCommand.cs @@ -30,25 +30,27 @@ public AttachmentValidator() public class AddAttachmentsHandler : IRequestHandler { - private readonly ITransactionRepository _transactionRepository; + private readonly IFinancialYearRepository _financialYearRepository; private readonly IAttachmentStorage _attachmentStorage; - public AddAttachmentsHandler(ITransactionRepository transactionRepository, IAttachmentStorage attachmentStorage) + public AddAttachmentsHandler(IFinancialYearRepository financialYearRepository, IAttachmentStorage attachmentStorage) { - _transactionRepository = transactionRepository; + _financialYearRepository = financialYearRepository; _attachmentStorage = attachmentStorage; } public async Task Handle(AddAttachmentsCommand request, CancellationToken cancellationToken) { - var transaction = await _transactionRepository.GetByIdAsync(request.TransactionId, cancellationToken) - ?? throw new ArgumentException($"No transaction found for Id {request.TransactionId}", nameof(request.TransactionId)); + var financialYear = await _financialYearRepository.GetFinancialYearByTransactionId(request.TransactionId, cancellationToken) + ?? throw new ArgumentException($"No financial year found for transactionId {request.TransactionId}", nameof(request.TransactionId)); + + var transaction = financialYear.Transactions.First(x => x.Id == request.TransactionId); var transactionAttachments = await StoreAttachmentsAsync(request, cancellationToken); - + transaction.AddAttachments(transactionAttachments); - await _transactionRepository.SaveAsync(cancellationToken); + await _financialYearRepository.SaveChangesAsync(cancellationToken); return Unit.Value; } diff --git a/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs b/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs index 673c2c45..aaabe05c 100644 --- a/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs +++ b/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs @@ -2,6 +2,7 @@ using Commands.Handlers.Transaction.AddAttachments; using Domain; +using Domain.Interfaces; using Persistence.Repositories; @@ -9,22 +10,29 @@ namespace Commands.Handlers.Transaction.AddCreditTransaction; public class AddCreditTransactionHandler : IRequestHandler { - private readonly ITransactionRepository _transactionRepository; + private readonly IFinancialYearRepository _financialYearRepository; private readonly IMediator _mediator; - public AddCreditTransactionHandler(ITransactionRepository transactionRepository, IMediator mediator) + public AddCreditTransactionHandler(IFinancialYearRepository financialYearRepository, IMediator mediator) { - _transactionRepository = transactionRepository; + _financialYearRepository = financialYearRepository; _mediator = mediator; } public async Task Handle(AddCreditTransactionCommand request, CancellationToken cancellationToken) { + + var financialYear = + await _financialYearRepository.GetFinancialYearByDateAsync(request.ReceivedDateTime, cancellationToken) + ?? throw new ArgumentException($"No financial year found for date {request.ReceivedDateTime}", nameof(request.ReceivedDateTime)); + var totalAmount = request.TransactionTypeAmounts.Sum(x => x.Amount); var transaction = new CreditTransaction(request.CounterPartyName, request.BankAccountId, totalAmount, request.ReceivedDateTime, request.Description, new List(), request.MemberId, request.TransactionTypeAmounts); - _transactionRepository.Add(transaction); - await _transactionRepository.SaveAsync(cancellationToken); + + financialYear.AddTransaction(transaction); +; + await _financialYearRepository.SaveChangesAsync(cancellationToken); await _mediator.Send(new AddAttachmentsCommand(transaction.Id, request.NewAttachmentFiles), cancellationToken); diff --git a/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs b/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs index 1b8d392a..7de8ddc3 100644 --- a/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs +++ b/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs @@ -1,31 +1,35 @@ using Commands.Handlers.Transaction.AddAttachments; using Domain; - -using Persistence.Repositories; +using Domain.Interfaces; namespace Commands.Handlers.Transaction.AddDebitTransaction; public class AddDebitTransactionHandler : IRequestHandler { - private readonly ITransactionRepository _transactionRepository; + private readonly IFinancialYearRepository _financialYearRepository; private readonly IMediator _mediator; - public AddDebitTransactionHandler(ITransactionRepository transactionRepository, IMediator mediator) + public AddDebitTransactionHandler(IFinancialYearRepository financialYearRepository, IMediator mediator) { - _transactionRepository = transactionRepository; + _financialYearRepository = financialYearRepository; _mediator = mediator; } public async Task Handle(AddDebitTransactionCommand request, CancellationToken cancellationToken) { + var financialYear = + await _financialYearRepository.GetFinancialYearByDateAsync(request.ReceivedDateTime, cancellationToken) + ?? throw new ArgumentException($"No financial year found for date {request.ReceivedDateTime}", nameof(request.ReceivedDateTime)); + var totalAmount = request.TransactionTypeAmounts.Sum(x => x.Amount); var transaction = new DebitTransaction(request.CounterPartyName, request.BankAccountId, totalAmount, request.ReceivedDateTime, request.Description, new List(), request.MemberId, request.TransactionTypeAmounts); - _transactionRepository.Add(transaction); - await _transactionRepository.SaveAsync(cancellationToken); + financialYear.AddTransaction(transaction); + + await _financialYearRepository.SaveChangesAsync(cancellationToken); await _mediator.Send(new AddAttachmentsCommand(transaction.Id, request.NewAttachmentFiles), cancellationToken); diff --git a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs index 26a34eaa..4ed54378 100644 --- a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs +++ b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs @@ -1,37 +1,45 @@ using Commands.Handlers.Transaction.AddAttachments; +using Domain.Interfaces; + using Persistence.Repositories; namespace Commands.Handlers.Transaction.EditTransaction; -public class EditTransactionHandler : IRequestHandler +public class EditTransactionHandler : IRequestHandler { - private readonly ITransactionRepository _transactionRepository; + private readonly IFinancialYearRepository _financialYearRepository; private readonly IMediator _mediator; - public EditTransactionHandler(ITransactionRepository transactionRepository, IMediator mediator) + public EditTransactionHandler(IFinancialYearRepository financialYearRepository, IMediator mediator) { - _transactionRepository = transactionRepository; + _financialYearRepository = financialYearRepository; _mediator = mediator; } - public async Task Handle(EditTransactionCommand request, CancellationToken cancellationToken) + public async Task Handle(EditTransactionCommand request, CancellationToken cancellationToken) { - var transaction = await _transactionRepository.GetByIdAsync(request.Id, cancellationToken) - ?? throw new ArgumentException($"No transaction found for Id {request.Id}", nameof(request.Id)); - - var totalAmount = request.TransactionTypeAmounts.Sum(x => x.Amount); - - transaction.ChangeCounterParty(request.CounterPartyName, request.MemberId); - transaction.ChangeBankAccountId(request.BankAccountId); - transaction.ChangeReceivedDateTime(request.ReceivedDateTime); - transaction.ChangeAmount(totalAmount, request.TransactionTypeAmounts); - transaction.ChangeDescription(request.Description); - - await _transactionRepository.SaveAsync(cancellationToken); - - await _mediator.Send(new AddAttachmentsCommand(transaction.Id, request.NewAttachmentFiles), cancellationToken); - - return transaction.Id; + var financialYear = + await _financialYearRepository.GetFinancialYearByTransactionId(request.Id, cancellationToken) + ?? throw new ArgumentException($"No financial year found for date {request.ReceivedDateTime}", nameof(request.ReceivedDateTime)); + + if(financialYear.StartDate >= request.ReceivedDateTime && + financialYear.EndDate <= request.ReceivedDateTime) + { + financialYear.ChangeTransaction(request.Id, request.CounterPartyName, request.MemberId, request.BankAccountId, + request.ReceivedDateTime, request.TransactionTypeAmounts, request.Description); + } + else + { + var transaction = financialYear.Transactions.First(x => x.Id == request.Id); + financialYear.AddTransaction(transaction); + financialYear.Transactions.Remove(transaction); + } + + + await _financialYearRepository.SaveChangesAsync(cancellationToken); + + await _mediator.Send(new AddAttachmentsCommand(request.Id, request.NewAttachmentFiles), cancellationToken); + } } diff --git a/Commands/Handlers/Transaction/EditTransaction/EditTransationCommand.cs b/Commands/Handlers/Transaction/EditTransaction/EditTransationCommand.cs index dd91b276..65c3020b 100644 --- a/Commands/Handlers/Transaction/EditTransaction/EditTransationCommand.cs +++ b/Commands/Handlers/Transaction/EditTransaction/EditTransationCommand.cs @@ -14,7 +14,7 @@ public record EditTransactionCommand( DateTimeOffset ReceivedDateTime, string Description, ICollection TransactionTypeAmounts, - ICollection NewAttachmentFiles) : IRequest; + ICollection NewAttachmentFiles) : IRequest; public class EditTransactionCommandValidator : AbstractValidator diff --git a/Domain/FinancialYear.cs b/Domain/FinancialYear.cs index 36510412..4ca0d7cb 100644 --- a/Domain/FinancialYear.cs +++ b/Domain/FinancialYear.cs @@ -1,4 +1,6 @@ -namespace Domain; +using Azure.Core; + +namespace Domain; public class FinancialYear { @@ -18,13 +20,13 @@ public FinancialYear(DateTimeOffset startDate, DateTimeOffset endDate, ICollecti public bool IsClosed { get; private set; } - public ICollection Transactions { get; set; } + public ICollection Transactions { get; private set; } public void Close() { if (IsClosed) { - throw new Exception("Financial year is already closed"); + throw new InvalidOperationException("Financial year is already closed"); } IsClosed = true; foreach (var transaction in Transactions) @@ -37,8 +39,26 @@ public void AddTransaction(Transaction transaction) { if (IsClosed) { - throw new Exception("Financial year is closed"); + throw new InvalidOperationException("Financial year is closed"); } Transactions.Add(transaction); } + + public void ChangeTransaction(Guid transactionId, string counterPartyName, Guid? memberId, Guid bankAccountId, + DateTimeOffset receivedDateTime, ICollection transactionTypeAmounts, string description) + { + if (IsClosed) + { + throw new InvalidOperationException("Financial year is already closed"); + } + + var transaction = Transactions.First(x => x.Id == transactionId); + var totalAmount = transactionTypeAmounts.Sum(x => x.Amount); + + transaction.ChangeCounterParty(counterPartyName, memberId); + transaction.ChangeBankAccountId(bankAccountId); + transaction.ChangeReceivedDateTime(receivedDateTime); + transaction.ChangeAmount(totalAmount, transactionTypeAmounts); + transaction.ChangeDescription(description); + } } \ No newline at end of file diff --git a/Domain/Interfaces/IFinancialYearRepository.cs b/Domain/Interfaces/IFinancialYearRepository.cs index 0bdb2ac4..d28f6381 100644 --- a/Domain/Interfaces/IFinancialYearRepository.cs +++ b/Domain/Interfaces/IFinancialYearRepository.cs @@ -13,4 +13,7 @@ public interface IFinancialYearRepository void Add(FinancialYear financialYear); Task SaveChangesAsync(CancellationToken cancellationToken); Task GetMostRecentAsync(CancellationToken cancellationToken); + + Task GetFinancialYearByTransactionId(Guid transactionId, CancellationToken cancellationToken); + Task GetFinancialYearByDateAsync(DateTimeOffset dateTime, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs new file mode 100644 index 00000000..a3514ae9 --- /dev/null +++ b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs @@ -0,0 +1,17 @@ +using Domain; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Persistence.EntityConfigurations; + +public class FinancialYearEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(x => x.StartDate).IsRequired(); + builder.Property(x => x.EndDate).IsRequired(); + builder.Property(x => x.IsClosed).IsRequired(); + builder.OwnsMany(x => x.Transactions); + } +} \ No newline at end of file diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index ae31e53c..931bc42d 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -21,10 +21,11 @@ public HaSpManContext(DbContextOptions options) public DbSet Members { get; set; } = null!; public DbSet BankAccounts { get; set; } = null!; public DbSet BankAccountsWithTotals { get; set; } = null!; - public DbSet Transactions { get; set; } = null!; - + public DbSet FinancialYears { get;set; } = null!; + public DbSet Transactions { get; set; } = null!; + public DbSet FinancialYearConfigurations { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs index 1404ed1b..1decbb6b 100644 --- a/Persistence/Repositories/FinancialYearRepository.cs +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -21,6 +21,13 @@ public FinancialYearRepository(IDbContextFactory contextFactory) return _context.FinancialYears.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); } + public Task GetTransactionAsync(Guid transactionId, CancellationToken cancellationToken) + { + return _context.FinancialYears + .Include(x => x.Transactions.Where(t => t.Id == transactionId)) + .FirstOrDefaultAsync(x => x.Transactions.Any(t => t.Id == transactionId), cancellationToken); + } + public void Add(FinancialYear financialYear) { _context.FinancialYears.Add(financialYear); @@ -35,4 +42,16 @@ public async Task SaveChangesAsync(CancellationToken cancellationToken) { return _context.FinancialYears.OrderByDescending(x => x.StartDate).FirstOrDefaultAsync(cancellationToken); } + + public Task GetFinancialYearByTransactionId(Guid transactionId, CancellationToken cancellationToken) + { + return _context.FinancialYears + .SingleOrDefaultAsync(x => x.Transactions.Any(t => t.Id == transactionId), cancellationToken); + } + + public Task GetFinancialYearByDateAsync(DateTimeOffset dateTime, CancellationToken cancellationToken) + { + return _context.FinancialYears.SingleOrDefaultAsync(x => x.StartDate >= dateTime && x.EndDate <= dateTime, + cancellationToken); + } } \ No newline at end of file diff --git a/Persistence/Repositories/TransactionRepository.cs b/Persistence/Repositories/TransactionRepository.cs index e2997db9..f2c98139 100644 --- a/Persistence/Repositories/TransactionRepository.cs +++ b/Persistence/Repositories/TransactionRepository.cs @@ -1,56 +1,57 @@ -using Domain; - -using Microsoft.EntityFrameworkCore; - -namespace Persistence.Repositories; - -public class TransactionRepository : ITransactionRepository -{ - private readonly HaSpManContext _context; - - public TransactionRepository(IDbContextFactory haSpManContext) - { - _context = haSpManContext.CreateDbContext(); - } - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken) - { - return await _context.Transactions - .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); - } - - - - public async Task> GetAllAsync(CancellationToken cancellationToken) - { - return await _context.Transactions.ToListAsync(cancellationToken); - } - - public void AddRange(IEnumerable transactions) - { - _context.Transactions.AddRange(transactions); - } - - public void Add(Transaction member) - { - _context.Transactions.Add(member); - } - - public void Remove(Transaction member) - { - _context.Transactions.Remove(member); - } - - public async Task SaveAsync(CancellationToken cancellationToken) - { - await _context.SaveChangesAsync(cancellationToken); - } -} -public interface ITransactionRepository -{ - Task GetByIdAsync(Guid id, CancellationToken cancellationToken); - Task> GetAllAsync(CancellationToken cancellationToken); - void AddRange(IEnumerable transactions); - void Add(Transaction member); - void Remove(Transaction member); - Task SaveAsync(CancellationToken cancellationToken); -} +//using Domain; + +//using Microsoft.EntityFrameworkCore; + +//namespace Persistence.Repositories; + +//public class TransactionRepository : ITransactionRepository +//{ +// private readonly HaSpManContext _context; + +// public TransactionRepository(IDbContextFactory haSpManContext) +// { +// _context = haSpManContext.CreateDbContext(); +// } +// public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken) +// { +// return await _context.FinancialYears +// .Select() +// .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); +// } + + + +// public async Task> GetAllAsync(CancellationToken cancellationToken) +// { +// return await _context.Transactions.ToListAsync(cancellationToken); +// } + +// public void AddRange(IEnumerable transactions) +// { +// _context.Transactions.AddRange(transactions); +// } + +// public void Add(Transaction member) +// { +// _context.Transactions.Add(member); +// } + +// public void Remove(Transaction member) +// { +// _context.Transactions.Remove(member); +// } + +// public async Task SaveAsync(CancellationToken cancellationToken) +// { +// await _context.SaveChangesAsync(cancellationToken); +// } +//} +//public interface ITransactionRepository +//{ +// Task GetByIdAsync(Guid id, CancellationToken cancellationToken); +// Task> GetAllAsync(CancellationToken cancellationToken); +// void AddRange(IEnumerable transactions); +// void Add(Transaction member); +// void Remove(Transaction member); +// Task SaveAsync(CancellationToken cancellationToken); +//} diff --git a/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs b/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs index feff0120..62ce9dae 100644 --- a/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs +++ b/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs @@ -31,6 +31,7 @@ public async Task Handle(AutocompleteCounterpa var counterParties = await context.Transactions + .AsNoTracking() .Where(x => x.MemberId == null && x.CounterPartyName.ToLower().Contains(request.SearchString.ToLower())) diff --git a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs index d64e5525..761a4778 100644 --- a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs +++ b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs @@ -1,28 +1,33 @@ using Domain.Interfaces; -using Persistence.Repositories; +using Microsoft.EntityFrameworkCore; + +using Persistence; using Types; -namespace Commands.Handlers.Transaction.GetAttachment; +namespace Queries.Transactions.GetAttachment; public record GetAttachmentQuery(Guid TransactionId, string FileName) : IRequest; public class GetAttachmentHandler : IRequestHandler { - private readonly ITransactionRepository _transactionRepository; + private readonly IDbContextFactory _dbContextFactory; private readonly IAttachmentStorage _attachmentStorage; - public GetAttachmentHandler(ITransactionRepository transactionRepository, IAttachmentStorage attachmentStorage) + public GetAttachmentHandler(IDbContextFactory dbContextFactory, IAttachmentStorage attachmentStorage) { - _transactionRepository = transactionRepository; + _dbContextFactory = dbContextFactory; _attachmentStorage = attachmentStorage; } public async Task Handle(GetAttachmentQuery request, CancellationToken cancellationToken) { - + var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); var transactionId = request.TransactionId; - var transaction = await _transactionRepository.GetByIdAsync(transactionId, cancellationToken) + var transaction = + await dbContext.Transactions + .AsNoTracking() + .SingleOrDefaultAsync(x => x.Id == transactionId, cancellationToken) ?? throw new ArgumentException($"No transaction found for Id {request.TransactionId}", nameof(request.TransactionId)); var attachment = transaction.Attachments.SingleOrDefault(x => x.Name == request.FileName) diff --git a/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs b/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs index bd6f4e37..3eaecc4c 100644 --- a/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs +++ b/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs @@ -23,7 +23,7 @@ public GetTransactionByIdHandler(IDbContextFactory contextFactor public async Task Handle(GetTransactionByIdQuery request, CancellationToken cancellationToken) { var context = await _contextFactory.CreateDbContextAsync(cancellationToken); - var transaction = await context.Transactions.SingleAsync(x => x.Id == request.Id, cancellationToken); + var transaction = await context.Transactions.AsNoTracking().SingleAsync(x => x.Id == request.Id, cancellationToken); return _mapper.Map(transaction); } diff --git a/Queries/Transactions/Handlers/GetTransactionsHandler.cs b/Queries/Transactions/Handlers/GetTransactionsHandler.cs index e24086db..7e7bd851 100644 --- a/Queries/Transactions/Handlers/GetTransactionsHandler.cs +++ b/Queries/Transactions/Handlers/GetTransactionsHandler.cs @@ -25,7 +25,7 @@ public GetTransactionsHandler(IDbContextFactory contextFactory, } public async Task> Handle(GetTransactionQuery request, CancellationToken cancellationToken) { - var context = _contextFactory.CreateDbContext(); + var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var transactions = context.Transactions.AsNoTracking() .Where(GetFilterCriteria(request.SearchString)); diff --git a/Web/Pages/Transactions/EditTransaction.razor b/Web/Pages/Transactions/EditTransaction.razor index 67070328..b235b5cf 100644 --- a/Web/Pages/Transactions/EditTransaction.razor +++ b/Web/Pages/Transactions/EditTransaction.razor @@ -70,7 +70,7 @@ .Select(x => new AttachmentFile(x.FileName, x.ContentType, x.UnsafePath)) .ToList()); - var response = await Mediator.Send(command); + await Mediator.Send(command); if (memberId.HasValue && expirationDate.HasValue && applyCalculation) { await Mediator.Send(new ExtendMembershipCommand(memberId.Value, expirationDate.Value)); diff --git a/Web/Pages/Transactions/TransactionForm.razor b/Web/Pages/Transactions/TransactionForm.razor index f4fd3b03..8de63217 100644 --- a/Web/Pages/Transactions/TransactionForm.razor +++ b/Web/Pages/Transactions/TransactionForm.razor @@ -4,7 +4,7 @@ @using Queries.Members.Handlers.GetMemberById @using Queries.Members.ViewModels @using Queries.Members.Handlers.AutocompleteMember -@using Commands.Handlers.Transaction.GetAttachment +@using Queries.Transactions.GetAttachment @inject IWebHostEnvironment Environment @inject IDialogService dialogService diff --git a/Web/Startup.cs b/Web/Startup.cs index fda45774..fa52b3cf 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -54,7 +54,6 @@ public void ConfigureServices(IServiceCollection services) services.Configure(Configuration.GetSection("Storage")); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddAutoMapper( From 68171c80bde2aebb041cb33aa0e0b5f79d470629 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sun, 25 Jun 2023 18:58:54 +0200 Subject: [PATCH 13/26] chore: Remove obsolete transactionrepository --- .../Repositories/TransactionRepository.cs | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 Persistence/Repositories/TransactionRepository.cs diff --git a/Persistence/Repositories/TransactionRepository.cs b/Persistence/Repositories/TransactionRepository.cs deleted file mode 100644 index f2c98139..00000000 --- a/Persistence/Repositories/TransactionRepository.cs +++ /dev/null @@ -1,57 +0,0 @@ -//using Domain; - -//using Microsoft.EntityFrameworkCore; - -//namespace Persistence.Repositories; - -//public class TransactionRepository : ITransactionRepository -//{ -// private readonly HaSpManContext _context; - -// public TransactionRepository(IDbContextFactory haSpManContext) -// { -// _context = haSpManContext.CreateDbContext(); -// } -// public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken) -// { -// return await _context.FinancialYears -// .Select() -// .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); -// } - - - -// public async Task> GetAllAsync(CancellationToken cancellationToken) -// { -// return await _context.Transactions.ToListAsync(cancellationToken); -// } - -// public void AddRange(IEnumerable transactions) -// { -// _context.Transactions.AddRange(transactions); -// } - -// public void Add(Transaction member) -// { -// _context.Transactions.Add(member); -// } - -// public void Remove(Transaction member) -// { -// _context.Transactions.Remove(member); -// } - -// public async Task SaveAsync(CancellationToken cancellationToken) -// { -// await _context.SaveChangesAsync(cancellationToken); -// } -//} -//public interface ITransactionRepository -//{ -// Task GetByIdAsync(Guid id, CancellationToken cancellationToken); -// Task> GetAllAsync(CancellationToken cancellationToken); -// void AddRange(IEnumerable transactions); -// void Add(Transaction member); -// void Remove(Transaction member); -// Task SaveAsync(CancellationToken cancellationToken); -//} From 568a59c2a427b4ebf2ff0fbaa5c416b984e331e3 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Mon, 26 Jun 2023 19:37:30 +0200 Subject: [PATCH 14/26] chore: remove Transactions dbset --- Persistence/HaSpManContext.cs | 2 -- .../AutocompleteMember/AutocompleteCounterpartyHandler.cs | 3 ++- Queries/Transactions/GetAttachment/GetAttachmentCommand.cs | 7 ++++--- Queries/Transactions/Handlers/GetTransactionByIdHandler.cs | 4 +++- Queries/Transactions/Handlers/GetTransactionsHandler.cs | 5 ++++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index 931bc42d..2150cdfc 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -24,8 +24,6 @@ public HaSpManContext(DbContextOptions options) public DbSet FinancialYears { get;set; } = null!; - public DbSet Transactions { get; set; } = null!; - public DbSet FinancialYearConfigurations { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) diff --git a/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs b/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs index 62ce9dae..4b0ae895 100644 --- a/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs +++ b/Queries/Members/Handlers/AutocompleteMember/AutocompleteCounterpartyHandler.cs @@ -30,7 +30,8 @@ public async Task Handle(AutocompleteCounterpa } - var counterParties = await context.Transactions + var counterParties = await context.FinancialYears + .SelectMany(x => x.Transactions) .AsNoTracking() .Where(x => x.MemberId == null && diff --git a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs index 761a4778..e0de9ee5 100644 --- a/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs +++ b/Queries/Transactions/GetAttachment/GetAttachmentCommand.cs @@ -22,10 +22,11 @@ public GetAttachmentHandler(IDbContextFactory dbContextFactory, } public async Task Handle(GetAttachmentQuery request, CancellationToken cancellationToken) { - var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); var transactionId = request.TransactionId; - var transaction = - await dbContext.Transactions + var transaction = + await context.FinancialYears + .SelectMany(x => x.Transactions) .AsNoTracking() .SingleOrDefaultAsync(x => x.Id == transactionId, cancellationToken) ?? throw new ArgumentException($"No transaction found for Id {request.TransactionId}", nameof(request.TransactionId)); diff --git a/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs b/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs index 3eaecc4c..219ed7cc 100644 --- a/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs +++ b/Queries/Transactions/Handlers/GetTransactionByIdHandler.cs @@ -23,7 +23,9 @@ public GetTransactionByIdHandler(IDbContextFactory contextFactor public async Task Handle(GetTransactionByIdQuery request, CancellationToken cancellationToken) { var context = await _contextFactory.CreateDbContextAsync(cancellationToken); - var transaction = await context.Transactions.AsNoTracking().SingleAsync(x => x.Id == request.Id, cancellationToken); + var transaction = await context.FinancialYears + .SelectMany(x => x.Transactions) + .AsNoTracking().SingleAsync(x => x.Id == request.Id, cancellationToken); return _mapper.Map(transaction); } diff --git a/Queries/Transactions/Handlers/GetTransactionsHandler.cs b/Queries/Transactions/Handlers/GetTransactionsHandler.cs index 7e7bd851..70dd6e00 100644 --- a/Queries/Transactions/Handlers/GetTransactionsHandler.cs +++ b/Queries/Transactions/Handlers/GetTransactionsHandler.cs @@ -26,7 +26,10 @@ public GetTransactionsHandler(IDbContextFactory contextFactory, public async Task> Handle(GetTransactionQuery request, CancellationToken cancellationToken) { var context = await _contextFactory.CreateDbContextAsync(cancellationToken); - var transactions = context.Transactions.AsNoTracking() + var transactions = + context.FinancialYears + .SelectMany(x => x.Transactions) + .AsNoTracking() .Where(GetFilterCriteria(request.SearchString)); var totalCount = await transactions.CountAsync(cancellationToken); From 3e0a3b0781f7b3e3eb83bd344c687a11e6f3213a Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Mon, 26 Jun 2023 20:46:14 +0200 Subject: [PATCH 15/26] feat: Adds migration --- .../FinancialYearEntityTypeConfiguration.cs | 4 +- .../TransactionConfigurations.cs | 13 +- ...ddFinancialYearAsAggregateRoot.Designer.cs | 401 ++++++++++++++++++ ...6184514_AddFinancialYearAsAggregateRoot.cs | 104 +++++ .../Migrations/HaSpManContextModelSnapshot.cs | 84 ++++ 5 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs create mode 100644 Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs diff --git a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs index a3514ae9..1d176333 100644 --- a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs +++ b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs @@ -12,6 +12,8 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.StartDate).IsRequired(); builder.Property(x => x.EndDate).IsRequired(); builder.Property(x => x.IsClosed).IsRequired(); - builder.OwnsMany(x => x.Transactions); + // Owned entity types cannot have inheritance hierarchies https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities#current-shortcomings + builder.HasMany(x => x.Transactions); + builder.Navigation(x => x.Transactions).AutoInclude(); } } \ No newline at end of file diff --git a/Persistence/EntityConfigurations/TransactionConfigurations.cs b/Persistence/EntityConfigurations/TransactionConfigurations.cs index 4e6d2529..0a5e1910 100644 --- a/Persistence/EntityConfigurations/TransactionConfigurations.cs +++ b/Persistence/EntityConfigurations/TransactionConfigurations.cs @@ -5,6 +5,18 @@ namespace Persistence.EntityConfigurations; +public class TransactionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + + builder.ToTable("Transactions"); + builder.HasDiscriminator("Discriminator") + .HasValue(nameof(DebitTransaction)) + .HasValue(nameof(CreditTransaction)); + } +} + public class CreditTransactionConfiguration : IEntityTypeConfiguration { @@ -59,7 +71,6 @@ public class DebitTransactionConfiguration : IEntityTypeConfiguration builder) { - builder.Property(x => x.Amount).IsRequired(); builder.Property(x => x.BankAccountId).IsRequired(); builder.Property(x => x.DateFiled).IsRequired(); diff --git a/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs b/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs new file mode 100644 index 00000000..4ecf821a --- /dev/null +++ b/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs @@ -0,0 +1,401 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Persistence; + +#nullable disable + +namespace Persistence.Migrations +{ + [DbContext(typeof(HaSpManContext))] + [Migration("20230626184514_AddFinancialYearAsAggregateRoot")] + partial class AddFinancialYearAsAggregateRoot + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("HaSpMan") + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar"); + + b.HasKey("Id"); + + b.ToTable("BankAccounts", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetimeoffset"); + + b.Property("IsClosed") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYears", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYearConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYearConfigurations", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("MembershipExpiryDate") + .HasColumnType("datetimeoffset"); + + b.Property("MembershipFee") + .HasColumnType("float"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("Members", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("CounterPartyName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("DateFiled") + .HasColumnType("datetimeoffset"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FinancialYearId") + .HasColumnType("uniqueidentifier"); + + b.Property("Locked") + .HasColumnType("bit"); + + b.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReceivedDateTime") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("FinancialYearId"); + + b.ToTable("Transactions", "HaSpMan"); + + b.HasDiscriminator("Discriminator").HasValue("Transaction"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfTransactions") + .HasColumnType("bigint"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.HasKey("BankAccountId"); + + b.ToTable((string)null); + + b.ToView("vwBankAccountTotals", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.CreditTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("CreditTransaction"); + }); + + modelBuilder.Entity("Domain.DebitTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("DebitTransaction"); + }); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("BankAccountId", "Id"); + + b1.ToTable("BankAccount_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("BankAccountId"); + }); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("MemberId", "Id"); + + b1.ToTable("Member_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.OwnsOne("Types.Address", "Address", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("HouseNumber") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("varchar"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar"); + + b1.HasKey("MemberId"); + + b1.ToTable("Members", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.Navigation("Address") + .IsRequired(); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.HasOne("Domain.FinancialYear", null) + .WithMany("Transactions") + .HasForeignKey("FinancialYearId"); + + b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("FullPath") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_Attachments", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b1.Property("TransactionType") + .HasColumnType("int"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_TransactionTypeAmounts", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.Navigation("Attachments"); + + b.Navigation("TransactionTypeAmounts"); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.HasOne("Domain.BankAccount", "Account") + .WithOne() + .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs b/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs new file mode 100644 index 00000000..31d774c8 --- /dev/null +++ b/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs @@ -0,0 +1,104 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Migrations; + +/// +public partial class AddFinancialYearAsAggregateRoot : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.AddColumn( + name: "Locked", + schema: "HaSpMan", + table: "Transactions", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "FinancialYearConfigurations", + schema: "HaSpMan", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + StartDate = table.Column(type: "datetimeoffset", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FinancialYearConfigurations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "FinancialYears", + schema: "HaSpMan", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + StartDate = table.Column(type: "datetimeoffset", nullable: false), + EndDate = table.Column(type: "datetimeoffset", nullable: false), + IsClosed = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FinancialYears", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Transactions_FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + column: "FinancialYearId"); + + migrationBuilder.AddForeignKey( + name: "FK_Transactions_FinancialYears_FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + column: "FinancialYearId", + principalSchema: "HaSpMan", + principalTable: "FinancialYears", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Transactions_FinancialYears_FinancialYearId", + schema: "HaSpMan", + table: "Transactions"); + + migrationBuilder.DropTable( + name: "FinancialYearConfigurations", + schema: "HaSpMan"); + + migrationBuilder.DropTable( + name: "FinancialYears", + schema: "HaSpMan"); + + migrationBuilder.DropIndex( + name: "IX_Transactions_FinancialYearId", + schema: "HaSpMan", + table: "Transactions"); + + migrationBuilder.DropColumn( + name: "FinancialYearId", + schema: "HaSpMan", + table: "Transactions"); + + migrationBuilder.DropColumn( + name: "Locked", + schema: "HaSpMan", + table: "Transactions"); + } +} \ No newline at end of file diff --git a/Persistence/Migrations/HaSpManContextModelSnapshot.cs b/Persistence/Migrations/HaSpManContextModelSnapshot.cs index a83d9550..7e9921ad 100644 --- a/Persistence/Migrations/HaSpManContextModelSnapshot.cs +++ b/Persistence/Migrations/HaSpManContextModelSnapshot.cs @@ -1,6 +1,10 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Persistence; #nullable disable @@ -40,6 +44,40 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("BankAccounts", "HaSpMan"); }); + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetimeoffset"); + + b.Property("IsClosed") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYears", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYearConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYearConfigurations", "HaSpMan"); + }); + modelBuilder.Entity("Domain.Member", b => { b.Property("Id") @@ -104,6 +142,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("FinancialYearId") + .HasColumnType("uniqueidentifier"); + + b.Property("Locked") + .HasColumnType("bit"); + b.Property("MemberId") .HasColumnType("uniqueidentifier"); @@ -112,6 +156,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("FinancialYearId"); + b.ToTable("Transactions", "HaSpMan"); b.HasDiscriminator("Discriminator").HasValue("Transaction"); @@ -119,6 +165,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.UseTphMappingStrategy(); }); + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfTransactions") + .HasColumnType("bigint"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.HasKey("BankAccountId"); + + b.ToTable((string)null); + + b.ToView("vwBankAccountTotals", "HaSpMan"); + }); + modelBuilder.Entity("Domain.CreditTransaction", b => { b.HasBaseType("Domain.Transaction"); @@ -250,6 +314,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.Transaction", b => { + b.HasOne("Domain.FinancialYear", null) + .WithMany("Transactions") + .HasForeignKey("FinancialYearId"); + b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => { b1.Property("TransactionId") @@ -308,6 +376,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("TransactionTypeAmounts"); }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.HasOne("Domain.BankAccount", "Account") + .WithOne() + .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Navigation("Transactions"); + }); #pragma warning restore 612, 618 } } From 40df1b41005883da45b55373f58f769576baf5e5 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Mon, 26 Jun 2023 20:53:41 +0200 Subject: [PATCH 16/26] fix: revert migration --- .../When_closing_a_financial_year.cs | 1 - Domain/FinancialYear.cs | 4 - Domain/Transaction.cs | 27 +- ...ddFinancialYearAsAggregateRoot.Designer.cs | 401 ------------------ ...6184514_AddFinancialYearAsAggregateRoot.cs | 104 ----- .../Migrations/HaSpManContextModelSnapshot.cs | 150 ++----- 6 files changed, 36 insertions(+), 651 deletions(-) delete mode 100644 Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs delete mode 100644 Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs diff --git a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs index 8d9111a7..9f1ff10d 100644 --- a/Commands.Test/FinancialYear/When_closing_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_closing_a_financial_year.cs @@ -59,7 +59,6 @@ public async Task It_should_mark_all_related_transactions_as_as_locked() await SUT.Handle(new CloseFinancialYearCommand(financialYear.Id), CancellationToken.None); financialYear.IsClosed.Should().BeTrue(); - financialYear.Transactions.Should().OnlyContain(x => x.Locked); } [Fact] diff --git a/Domain/FinancialYear.cs b/Domain/FinancialYear.cs index 4ca0d7cb..65097db1 100644 --- a/Domain/FinancialYear.cs +++ b/Domain/FinancialYear.cs @@ -29,10 +29,6 @@ public void Close() throw new InvalidOperationException("Financial year is already closed"); } IsClosed = true; - foreach (var transaction in Transactions) - { - transaction.Lock(); - } } public void AddTransaction(Transaction transaction) diff --git a/Domain/Transaction.cs b/Domain/Transaction.cs index 34050a9c..cd574ddd 100644 --- a/Domain/Transaction.cs +++ b/Domain/Transaction.cs @@ -61,8 +61,6 @@ protected Transaction( public void ChangeCounterParty(string counterPartyName, Guid? memberId) { - - AssertTransactionIsNotLocked(); if (memberId == MemberId && counterPartyName == CounterPartyName) { return; @@ -74,8 +72,6 @@ public void ChangeCounterParty(string counterPartyName, Guid? memberId) public void ChangeBankAccountId(Guid bankAccountId) { - - AssertTransactionIsNotLocked(); if (bankAccountId == BankAccountId) { return; @@ -86,8 +82,6 @@ public void ChangeBankAccountId(Guid bankAccountId) public void ChangeReceivedDateTime(DateTimeOffset receivedDateTime) { - - AssertTransactionIsNotLocked(); if (receivedDateTime > DateTimeOffset.Now) { throw new ArgumentException($"Received date is set to be in the future: {receivedDateTime}", @@ -104,8 +98,6 @@ public void ChangeReceivedDateTime(DateTimeOffset receivedDateTime) public void ChangeAmount(decimal amount, ICollection transactionTypeAmounts) { - - AssertTransactionIsNotLocked(); var sumOfTransactionTypeAmounts = transactionTypeAmounts.Sum(x => x.Amount); if (amount != sumOfTransactionTypeAmounts) { @@ -124,8 +116,6 @@ public void ChangeAmount(decimal amount, ICollection tran public void ChangeDescription(string description) { - - AssertTransactionIsNotLocked(); if (description == Description) { return; @@ -136,27 +126,12 @@ public void ChangeDescription(string description) public void AddAttachments(ICollection attachments) { - AssertTransactionIsNotLocked(); foreach (var attachment in attachments) { Attachments.Add(attachment); } } - - private void AssertTransactionIsNotLocked() - { - if (Locked) - { - throw new InvalidOperationException("Transaction is locked"); - } - } - - public void Lock() - { - Locked = true; - } - - public bool Locked { get; private set; } + } public class DebitTransaction : Transaction { diff --git a/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs b/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs deleted file mode 100644 index 4ecf821a..00000000 --- a/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.Designer.cs +++ /dev/null @@ -1,401 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Persistence; - -#nullable disable - -namespace Persistence.Migrations -{ - [DbContext(typeof(HaSpManContext))] - [Migration("20230626184514_AddFinancialYearAsAggregateRoot")] - partial class AddFinancialYearAsAggregateRoot - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("HaSpMan") - .HasAnnotation("ProductVersion", "7.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Domain.BankAccount", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("AccountNumber") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("varchar"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar"); - - b.HasKey("Id"); - - b.ToTable("BankAccounts", "HaSpMan"); - }); - - modelBuilder.Entity("Domain.FinancialYear", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("EndDate") - .HasColumnType("datetimeoffset"); - - b.Property("IsClosed") - .HasColumnType("bit"); - - b.Property("StartDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("FinancialYears", "HaSpMan"); - }); - - modelBuilder.Entity("Domain.FinancialYearConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("StartDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("FinancialYearConfigurations", "HaSpMan"); - }); - - modelBuilder.Entity("Domain.Member", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Email") - .HasMaxLength(100) - .HasColumnType("varchar"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); - - b.Property("MembershipExpiryDate") - .HasColumnType("datetimeoffset"); - - b.Property("MembershipFee") - .HasColumnType("float"); - - b.Property("PhoneNumber") - .HasMaxLength(50) - .HasColumnType("varchar"); - - b.HasKey("Id"); - - b.ToTable("Members", "HaSpMan"); - }); - - modelBuilder.Entity("Domain.Transaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Amount") - .HasColumnType("decimal(18,2)"); - - b.Property("BankAccountId") - .HasColumnType("uniqueidentifier"); - - b.Property("CounterPartyName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("nvarchar(120)"); - - b.Property("DateFiled") - .HasColumnType("datetimeoffset"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Discriminator") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("FinancialYearId") - .HasColumnType("uniqueidentifier"); - - b.Property("Locked") - .HasColumnType("bit"); - - b.Property("MemberId") - .HasColumnType("uniqueidentifier"); - - b.Property("ReceivedDateTime") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.HasIndex("FinancialYearId"); - - b.ToTable("Transactions", "HaSpMan"); - - b.HasDiscriminator("Discriminator").HasValue("Transaction"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => - { - b.Property("BankAccountId") - .HasColumnType("uniqueidentifier"); - - b.Property("NumberOfTransactions") - .HasColumnType("bigint"); - - b.Property("Total") - .HasColumnType("decimal(18,2)"); - - b.HasKey("BankAccountId"); - - b.ToTable((string)null); - - b.ToView("vwBankAccountTotals", "HaSpMan"); - }); - - modelBuilder.Entity("Domain.CreditTransaction", b => - { - b.HasBaseType("Domain.Transaction"); - - b.HasDiscriminator().HasValue("CreditTransaction"); - }); - - modelBuilder.Entity("Domain.DebitTransaction", b => - { - b.HasBaseType("Domain.Transaction"); - - b.HasDiscriminator().HasValue("DebitTransaction"); - }); - - modelBuilder.Entity("Domain.BankAccount", b => - { - b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => - { - b1.Property("BankAccountId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("varchar"); - - b1.Property("PerformedBy") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("varchar"); - - b1.Property("Timestamp") - .HasColumnType("datetimeoffset"); - - b1.HasKey("BankAccountId", "Id"); - - b1.ToTable("BankAccount_AuditEvents", "HaSpMan"); - - b1.WithOwner() - .HasForeignKey("BankAccountId"); - }); - - b.Navigation("AuditEvents"); - }); - - modelBuilder.Entity("Domain.Member", b => - { - b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => - { - b1.Property("MemberId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("varchar"); - - b1.Property("PerformedBy") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("varchar"); - - b1.Property("Timestamp") - .HasColumnType("datetimeoffset"); - - b1.HasKey("MemberId", "Id"); - - b1.ToTable("Member_AuditEvents", "HaSpMan"); - - b1.WithOwner() - .HasForeignKey("MemberId"); - }); - - b.OwnsOne("Types.Address", "Address", b1 => - { - b1.Property("MemberId") - .HasColumnType("uniqueidentifier"); - - b1.Property("City") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); - - b1.Property("HouseNumber") - .IsRequired() - .HasMaxLength(15) - .HasColumnType("varchar"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar"); - - b1.HasKey("MemberId"); - - b1.ToTable("Members", "HaSpMan"); - - b1.WithOwner() - .HasForeignKey("MemberId"); - }); - - b.Navigation("Address") - .IsRequired(); - - b.Navigation("AuditEvents"); - }); - - modelBuilder.Entity("Domain.Transaction", b => - { - b.HasOne("Domain.FinancialYear", null) - .WithMany("Transactions") - .HasForeignKey("FinancialYearId"); - - b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => - { - b1.Property("TransactionId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("FullPath") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b1.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("TransactionId", "Id"); - - b1.ToTable("Transaction_Attachments", "HaSpMan"); - - b1.WithOwner() - .HasForeignKey("TransactionId"); - }); - - b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => - { - b1.Property("TransactionId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("Amount") - .HasColumnType("decimal(18,2)"); - - b1.Property("TransactionType") - .HasColumnType("int"); - - b1.HasKey("TransactionId", "Id"); - - b1.ToTable("Transaction_TransactionTypeAmounts", "HaSpMan"); - - b1.WithOwner() - .HasForeignKey("TransactionId"); - }); - - b.Navigation("Attachments"); - - b.Navigation("TransactionTypeAmounts"); - }); - - modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => - { - b.HasOne("Domain.BankAccount", "Account") - .WithOne() - .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Domain.FinancialYear", b => - { - b.Navigation("Transactions"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs b/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs deleted file mode 100644 index 31d774c8..00000000 --- a/Persistence/Migrations/20230626184514_AddFinancialYearAsAggregateRoot.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Persistence.Migrations; - -/// -public partial class AddFinancialYearAsAggregateRoot : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "FinancialYearId", - schema: "HaSpMan", - table: "Transactions", - type: "uniqueidentifier", - nullable: true); - - migrationBuilder.AddColumn( - name: "Locked", - schema: "HaSpMan", - table: "Transactions", - type: "bit", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateTable( - name: "FinancialYearConfigurations", - schema: "HaSpMan", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - StartDate = table.Column(type: "datetimeoffset", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_FinancialYearConfigurations", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "FinancialYears", - schema: "HaSpMan", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - StartDate = table.Column(type: "datetimeoffset", nullable: false), - EndDate = table.Column(type: "datetimeoffset", nullable: false), - IsClosed = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_FinancialYears", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_Transactions_FinancialYearId", - schema: "HaSpMan", - table: "Transactions", - column: "FinancialYearId"); - - migrationBuilder.AddForeignKey( - name: "FK_Transactions_FinancialYears_FinancialYearId", - schema: "HaSpMan", - table: "Transactions", - column: "FinancialYearId", - principalSchema: "HaSpMan", - principalTable: "FinancialYears", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Transactions_FinancialYears_FinancialYearId", - schema: "HaSpMan", - table: "Transactions"); - - migrationBuilder.DropTable( - name: "FinancialYearConfigurations", - schema: "HaSpMan"); - - migrationBuilder.DropTable( - name: "FinancialYears", - schema: "HaSpMan"); - - migrationBuilder.DropIndex( - name: "IX_Transactions_FinancialYearId", - schema: "HaSpMan", - table: "Transactions"); - - migrationBuilder.DropColumn( - name: "FinancialYearId", - schema: "HaSpMan", - table: "Transactions"); - - migrationBuilder.DropColumn( - name: "Locked", - schema: "HaSpMan", - table: "Transactions"); - } -} \ No newline at end of file diff --git a/Persistence/Migrations/HaSpManContextModelSnapshot.cs b/Persistence/Migrations/HaSpManContextModelSnapshot.cs index 7e9921ad..3fc496bc 100644 --- a/Persistence/Migrations/HaSpManContextModelSnapshot.cs +++ b/Persistence/Migrations/HaSpManContextModelSnapshot.cs @@ -44,40 +44,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("BankAccounts", "HaSpMan"); }); - modelBuilder.Entity("Domain.FinancialYear", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("EndDate") - .HasColumnType("datetimeoffset"); - - b.Property("IsClosed") - .HasColumnType("bit"); - - b.Property("StartDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("FinancialYears", "HaSpMan"); - }); - - modelBuilder.Entity("Domain.FinancialYearConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("StartDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("FinancialYearConfigurations", "HaSpMan"); - }); - modelBuilder.Entity("Domain.Member", b => { b.Property("Id") @@ -142,12 +108,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("FinancialYearId") - .HasColumnType("uniqueidentifier"); - - b.Property("Locked") - .HasColumnType("bit"); - b.Property("MemberId") .HasColumnType("uniqueidentifier"); @@ -156,8 +116,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("FinancialYearId"); - b.ToTable("Transactions", "HaSpMan"); b.HasDiscriminator("Discriminator").HasValue("Transaction"); @@ -165,24 +123,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.UseTphMappingStrategy(); }); - modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => - { - b.Property("BankAccountId") - .HasColumnType("uniqueidentifier"); - - b.Property("NumberOfTransactions") - .HasColumnType("bigint"); - - b.Property("Total") - .HasColumnType("decimal(18,2)"); - - b.HasKey("BankAccountId"); - - b.ToTable((string)null); - - b.ToView("vwBankAccountTotals", "HaSpMan"); - }); - modelBuilder.Entity("Domain.CreditTransaction", b => { b.HasBaseType("Domain.Transaction"); @@ -199,7 +139,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.BankAccount", b => { - b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + b.OwnsMany("Domain.BankAccount.AuditEvents#Types.AuditEvent", "AuditEvents", b1 => { b1.Property("BankAccountId") .HasColumnType("uniqueidentifier"); @@ -236,71 +176,71 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.Member", b => { - b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + b.OwnsOne("Domain.Member.Address#Types.Address", "Address", b1 => { b1.Property("MemberId") .HasColumnType("uniqueidentifier"); - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); + b1.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + b1.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); - b1.Property("Description") + b1.Property("HouseNumber") .IsRequired() - .HasMaxLength(1000) + .HasMaxLength(15) .HasColumnType("varchar"); - b1.Property("PerformedBy") + b1.Property("Street") .IsRequired() - .HasMaxLength(100) + .HasMaxLength(200) .HasColumnType("varchar"); - b1.Property("Timestamp") - .HasColumnType("datetimeoffset"); + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar"); - b1.HasKey("MemberId", "Id"); + b1.HasKey("MemberId"); - b1.ToTable("Member_AuditEvents", "HaSpMan"); + b1.ToTable("Members", "HaSpMan"); b1.WithOwner() .HasForeignKey("MemberId"); }); - b.OwnsOne("Types.Address", "Address", b1 => + b.OwnsMany("Domain.Member.AuditEvents#Types.AuditEvent", "AuditEvents", b1 => { b1.Property("MemberId") .HasColumnType("uniqueidentifier"); - b1.Property("City") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); - b1.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - b1.Property("HouseNumber") + b1.Property("Description") .IsRequired() - .HasMaxLength(15) + .HasMaxLength(1000) .HasColumnType("varchar"); - b1.Property("Street") + b1.Property("PerformedBy") .IsRequired() - .HasMaxLength(200) + .HasMaxLength(100) .HasColumnType("varchar"); - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar"); + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); - b1.HasKey("MemberId"); + b1.HasKey("MemberId", "Id"); - b1.ToTable("Members", "HaSpMan"); + b1.ToTable("Member_AuditEvents", "HaSpMan"); b1.WithOwner() .HasForeignKey("MemberId"); @@ -314,11 +254,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.Transaction", b => { - b.HasOne("Domain.FinancialYear", null) - .WithMany("Transactions") - .HasForeignKey("FinancialYearId"); - - b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => + b.OwnsMany("Domain.Transaction.Attachments#Domain.TransactionAttachment", "Attachments", b1 => { b1.Property("TransactionId") .HasColumnType("uniqueidentifier"); @@ -347,7 +283,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("TransactionId"); }); - b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => + b.OwnsMany("Domain.Transaction.TransactionTypeAmounts#Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => { b1.Property("TransactionId") .HasColumnType("uniqueidentifier"); @@ -376,22 +312,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("TransactionTypeAmounts"); }); - - modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => - { - b.HasOne("Domain.BankAccount", "Account") - .WithOne() - .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("Domain.FinancialYear", b => - { - b.Navigation("Transactions"); - }); #pragma warning restore 612, 618 } } From 56745b487b4f023889676be1b99bae72da308aba Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Mon, 26 Jun 2023 21:09:15 +0200 Subject: [PATCH 17/26] feat: Adds migration --- Persistence/HaSpManContext.cs | 7 +- ...ddFinancialYearAsAggregateRoot.Designer.cs | 398 ++++++++++++++++++ ...6190616_AddFinancialYearAsAggregateRoot.cs | 92 ++++ .../Migrations/HaSpManContextModelSnapshot.cs | 147 +++++-- Web/Startup.cs | 2 + 5 files changed, 606 insertions(+), 40 deletions(-) create mode 100644 Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs create mode 100644 Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index 2150cdfc..52cd5c24 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -28,11 +28,8 @@ public HaSpManContext(DbContextOptions options) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable("__EFMigrationsHistory", Schema.HaSpMan)); - } - + optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable("__EFMigrationsHistory", Schema.HaSpMan)); + } protected override void OnModelCreating(ModelBuilder builder) diff --git a/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs b/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs new file mode 100644 index 00000000..e8ad5aae --- /dev/null +++ b/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs @@ -0,0 +1,398 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Persistence; + +#nullable disable + +namespace Persistence.Migrations +{ + [DbContext(typeof(HaSpManContext))] + [Migration("20230626190616_AddFinancialYearAsAggregateRoot")] + partial class AddFinancialYearAsAggregateRoot + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("HaSpMan") + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar"); + + b.HasKey("Id"); + + b.ToTable("BankAccounts", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetimeoffset"); + + b.Property("IsClosed") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYears", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYearConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYearConfigurations", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("MembershipExpiryDate") + .HasColumnType("datetimeoffset"); + + b.Property("MembershipFee") + .HasColumnType("float"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("Members", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("CounterPartyName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("DateFiled") + .HasColumnType("datetimeoffset"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FinancialYearId") + .HasColumnType("uniqueidentifier"); + + b.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReceivedDateTime") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("FinancialYearId"); + + b.ToTable("Transactions", "HaSpMan"); + + b.HasDiscriminator("Discriminator").HasValue("Transaction"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfTransactions") + .HasColumnType("bigint"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.HasKey("BankAccountId"); + + b.ToTable((string)null); + + b.ToView("vwBankAccountTotals", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.CreditTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("CreditTransaction"); + }); + + modelBuilder.Entity("Domain.DebitTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("DebitTransaction"); + }); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("BankAccountId", "Id"); + + b1.ToTable("BankAccount_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("BankAccountId"); + }); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("MemberId", "Id"); + + b1.ToTable("Member_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.OwnsOne("Types.Address", "Address", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("HouseNumber") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("varchar"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar"); + + b1.HasKey("MemberId"); + + b1.ToTable("Members", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.Navigation("Address") + .IsRequired(); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.HasOne("Domain.FinancialYear", null) + .WithMany("Transactions") + .HasForeignKey("FinancialYearId"); + + b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("FullPath") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_Attachments", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b1.Property("TransactionType") + .HasColumnType("int"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_TransactionTypeAmounts", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.Navigation("Attachments"); + + b.Navigation("TransactionTypeAmounts"); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.HasOne("Domain.BankAccount", "Account") + .WithOne() + .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs b/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs new file mode 100644 index 00000000..02c6b737 --- /dev/null +++ b/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs @@ -0,0 +1,92 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Migrations +{ + /// + public partial class AddFinancialYearAsAggregateRoot : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "FinancialYearConfigurations", + schema: "HaSpMan", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + StartDate = table.Column(type: "datetimeoffset", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FinancialYearConfigurations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "FinancialYears", + schema: "HaSpMan", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + StartDate = table.Column(type: "datetimeoffset", nullable: false), + EndDate = table.Column(type: "datetimeoffset", nullable: false), + IsClosed = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FinancialYears", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Transactions_FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + column: "FinancialYearId"); + + migrationBuilder.AddForeignKey( + name: "FK_Transactions_FinancialYears_FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + column: "FinancialYearId", + principalSchema: "HaSpMan", + principalTable: "FinancialYears", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Transactions_FinancialYears_FinancialYearId", + schema: "HaSpMan", + table: "Transactions"); + + migrationBuilder.DropTable( + name: "FinancialYearConfigurations", + schema: "HaSpMan"); + + migrationBuilder.DropTable( + name: "FinancialYears", + schema: "HaSpMan"); + + migrationBuilder.DropIndex( + name: "IX_Transactions_FinancialYearId", + schema: "HaSpMan", + table: "Transactions"); + + migrationBuilder.DropColumn( + name: "FinancialYearId", + schema: "HaSpMan", + table: "Transactions"); + } + } +} diff --git a/Persistence/Migrations/HaSpManContextModelSnapshot.cs b/Persistence/Migrations/HaSpManContextModelSnapshot.cs index 3fc496bc..c0fca612 100644 --- a/Persistence/Migrations/HaSpManContextModelSnapshot.cs +++ b/Persistence/Migrations/HaSpManContextModelSnapshot.cs @@ -44,6 +44,40 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("BankAccounts", "HaSpMan"); }); + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetimeoffset"); + + b.Property("IsClosed") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYears", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYearConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYearConfigurations", "HaSpMan"); + }); + modelBuilder.Entity("Domain.Member", b => { b.Property("Id") @@ -108,6 +142,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("FinancialYearId") + .HasColumnType("uniqueidentifier"); + b.Property("MemberId") .HasColumnType("uniqueidentifier"); @@ -116,6 +153,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("FinancialYearId"); + b.ToTable("Transactions", "HaSpMan"); b.HasDiscriminator("Discriminator").HasValue("Transaction"); @@ -123,6 +162,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.UseTphMappingStrategy(); }); + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfTransactions") + .HasColumnType("bigint"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.HasKey("BankAccountId"); + + b.ToTable((string)null); + + b.ToView("vwBankAccountTotals", "HaSpMan"); + }); + modelBuilder.Entity("Domain.CreditTransaction", b => { b.HasBaseType("Domain.Transaction"); @@ -139,7 +196,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.BankAccount", b => { - b.OwnsMany("Domain.BankAccount.AuditEvents#Types.AuditEvent", "AuditEvents", b1 => + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => { b1.Property("BankAccountId") .HasColumnType("uniqueidentifier"); @@ -176,71 +233,71 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.Member", b => { - b.OwnsOne("Domain.Member.Address#Types.Address", "Address", b1 => + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => { b1.Property("MemberId") .HasColumnType("uniqueidentifier"); - b1.Property("City") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); - b1.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar"); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - b1.Property("HouseNumber") + b1.Property("Description") .IsRequired() - .HasMaxLength(15) + .HasMaxLength(1000) .HasColumnType("varchar"); - b1.Property("Street") + b1.Property("PerformedBy") .IsRequired() - .HasMaxLength(200) + .HasMaxLength(100) .HasColumnType("varchar"); - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar"); + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); - b1.HasKey("MemberId"); + b1.HasKey("MemberId", "Id"); - b1.ToTable("Members", "HaSpMan"); + b1.ToTable("Member_AuditEvents", "HaSpMan"); b1.WithOwner() .HasForeignKey("MemberId"); }); - b.OwnsMany("Domain.Member.AuditEvents#Types.AuditEvent", "AuditEvents", b1 => + b.OwnsOne("Types.Address", "Address", b1 => { b1.Property("MemberId") .HasColumnType("uniqueidentifier"); - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); + b1.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + b1.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); - b1.Property("Description") + b1.Property("HouseNumber") .IsRequired() - .HasMaxLength(1000) + .HasMaxLength(15) .HasColumnType("varchar"); - b1.Property("PerformedBy") + b1.Property("Street") .IsRequired() - .HasMaxLength(100) + .HasMaxLength(200) .HasColumnType("varchar"); - b1.Property("Timestamp") - .HasColumnType("datetimeoffset"); + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar"); - b1.HasKey("MemberId", "Id"); + b1.HasKey("MemberId"); - b1.ToTable("Member_AuditEvents", "HaSpMan"); + b1.ToTable("Members", "HaSpMan"); b1.WithOwner() .HasForeignKey("MemberId"); @@ -254,7 +311,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Domain.Transaction", b => { - b.OwnsMany("Domain.Transaction.Attachments#Domain.TransactionAttachment", "Attachments", b1 => + b.HasOne("Domain.FinancialYear", null) + .WithMany("Transactions") + .HasForeignKey("FinancialYearId"); + + b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => { b1.Property("TransactionId") .HasColumnType("uniqueidentifier"); @@ -283,7 +344,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("TransactionId"); }); - b.OwnsMany("Domain.Transaction.TransactionTypeAmounts#Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => + b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => { b1.Property("TransactionId") .HasColumnType("uniqueidentifier"); @@ -312,6 +373,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("TransactionTypeAmounts"); }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.HasOne("Domain.BankAccount", "Account") + .WithOne() + .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Navigation("Transactions"); + }); #pragma warning restore 612, 618 } } diff --git a/Web/Startup.cs b/Web/Startup.cs index fa52b3cf..bdd14a0b 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -56,6 +56,8 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddAutoMapper( typeof(MapperProfiles.MemberProfile), typeof(MapperProfiles.TransactionProfile), From 70403af51158e5a264be1e81376de0b2037ba962 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 21:00:41 +0200 Subject: [PATCH 18/26] feat: Adds migration to make FinancialYearId non-nullable --- .../FinancialYearEntityTypeConfiguration.cs | 2 +- ...dFinancialYearAsAggregateRoot.Designer.cs} | 2 +- ...183144_AddFinancialYearAsAggregateRoot.cs} | 4 + ...nsactionsBelongToFinancialYear.Designer.cs | 398 ++++++++++++++++++ ...84205_TransactionsBelongToFinancialYear.cs | 36 ++ .../Repositories/FinancialYearRepository.cs | 2 +- 6 files changed, 441 insertions(+), 3 deletions(-) rename Persistence/Migrations/{20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs => 20230728183144_AddFinancialYearAsAggregateRoot.Designer.cs} (99%) rename Persistence/Migrations/{20230626190616_AddFinancialYearAsAggregateRoot.cs => 20230728183144_AddFinancialYearAsAggregateRoot.cs} (99%) create mode 100644 Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.Designer.cs create mode 100644 Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs diff --git a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs index 1d176333..7c74a321 100644 --- a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs +++ b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs @@ -13,7 +13,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.EndDate).IsRequired(); builder.Property(x => x.IsClosed).IsRequired(); // Owned entity types cannot have inheritance hierarchies https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities#current-shortcomings - builder.HasMany(x => x.Transactions); + builder.HasMany(x => x.Transactions).WithOne(); builder.Navigation(x => x.Transactions).AutoInclude(); } } \ No newline at end of file diff --git a/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs b/Persistence/Migrations/20230728183144_AddFinancialYearAsAggregateRoot.Designer.cs similarity index 99% rename from Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs rename to Persistence/Migrations/20230728183144_AddFinancialYearAsAggregateRoot.Designer.cs index e8ad5aae..df9424d6 100644 --- a/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.Designer.cs +++ b/Persistence/Migrations/20230728183144_AddFinancialYearAsAggregateRoot.Designer.cs @@ -12,7 +12,7 @@ namespace Persistence.Migrations { [DbContext(typeof(HaSpManContext))] - [Migration("20230626190616_AddFinancialYearAsAggregateRoot")] + [Migration("20230728183144_AddFinancialYearAsAggregateRoot")] partial class AddFinancialYearAsAggregateRoot { /// diff --git a/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs b/Persistence/Migrations/20230728183144_AddFinancialYearAsAggregateRoot.cs similarity index 99% rename from Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs rename to Persistence/Migrations/20230728183144_AddFinancialYearAsAggregateRoot.cs index 02c6b737..bd61d717 100644 --- a/Persistence/Migrations/20230626190616_AddFinancialYearAsAggregateRoot.cs +++ b/Persistence/Migrations/20230728183144_AddFinancialYearAsAggregateRoot.cs @@ -60,6 +60,10 @@ protected override void Up(MigrationBuilder migrationBuilder) principalSchema: "HaSpMan", principalTable: "FinancialYears", principalColumn: "Id"); + + + + } /// diff --git a/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.Designer.cs b/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.Designer.cs new file mode 100644 index 00000000..98826678 --- /dev/null +++ b/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.Designer.cs @@ -0,0 +1,398 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Persistence; + +#nullable disable + +namespace Persistence.Migrations +{ + [DbContext(typeof(HaSpManContext))] + [Migration("20230728184205_TransactionsBelongToFinancialYear")] + partial class TransactionsBelongToFinancialYear + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("HaSpMan") + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar"); + + b.HasKey("Id"); + + b.ToTable("BankAccounts", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetimeoffset"); + + b.Property("IsClosed") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYears", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYearConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYearConfigurations", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("MembershipExpiryDate") + .HasColumnType("datetimeoffset"); + + b.Property("MembershipFee") + .HasColumnType("float"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("Members", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("CounterPartyName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("DateFiled") + .HasColumnType("datetimeoffset"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FinancialYearId") + .HasColumnType("uniqueidentifier"); + + b.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReceivedDateTime") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("FinancialYearId"); + + b.ToTable("Transactions", "HaSpMan"); + + b.HasDiscriminator("Discriminator").HasValue("Transaction"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfTransactions") + .HasColumnType("bigint"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.HasKey("BankAccountId"); + + b.ToTable((string)null); + + b.ToView("vwBankAccountTotals", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.CreditTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("CreditTransaction"); + }); + + modelBuilder.Entity("Domain.DebitTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("DebitTransaction"); + }); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("BankAccountId", "Id"); + + b1.ToTable("BankAccount_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("BankAccountId"); + }); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("MemberId", "Id"); + + b1.ToTable("Member_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.OwnsOne("Types.Address", "Address", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("HouseNumber") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("varchar"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar"); + + b1.HasKey("MemberId"); + + b1.ToTable("Members", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.Navigation("Address") + .IsRequired(); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.HasOne("Domain.FinancialYear", null) + .WithMany("Transactions") + .HasForeignKey("FinancialYearId"); + + b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("FullPath") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_Attachments", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b1.Property("TransactionType") + .HasColumnType("int"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_TransactionTypeAmounts", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.Navigation("Attachments"); + + b.Navigation("TransactionTypeAmounts"); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.HasOne("Domain.BankAccount", "Account") + .WithOne() + .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs b/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs new file mode 100644 index 00000000..5e239fb8 --- /dev/null +++ b/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Migrations; + +/// +public partial class TransactionsBelongToFinancialYear : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("INSERT INTO " + + "HaspMan.FinancialYears (Id, StartDate, EndDate, IsClosed) " + + "VALUES (newId(), CAST('2022-09-01 00:00:00.0000000 +01:00' AS DATETIMEOFFSET),CAST('2023-08-31 00:00:00.0000000 +01:00' AS DATETIMEOFFSET), 0)"); + + migrationBuilder.DropIndex("IX_Transactions_FinancialYearId", schema: "HaspMan", table: "Transactions"); + migrationBuilder.AlterColumn( + "FinancialYearId", + schema:"HaspMan", + table: "Transactions", + nullable: false); + migrationBuilder.CreateIndex( + name: "IX_Transactions_FinancialYearId", + schema: "HaSpMan", + table: "Transactions", + column: "FinancialYearId"); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } +} \ No newline at end of file diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs index 1decbb6b..8abb0e3c 100644 --- a/Persistence/Repositories/FinancialYearRepository.cs +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -51,7 +51,7 @@ public async Task SaveChangesAsync(CancellationToken cancellationToken) public Task GetFinancialYearByDateAsync(DateTimeOffset dateTime, CancellationToken cancellationToken) { - return _context.FinancialYears.SingleOrDefaultAsync(x => x.StartDate >= dateTime && x.EndDate <= dateTime, + return _context.FinancialYears.SingleOrDefaultAsync(x => x.StartDate <= dateTime && x.EndDate >= dateTime, cancellationToken); } } \ No newline at end of file From cdf7da325a0e5f0f357853cef8cded00f87865a2 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 21:30:17 +0200 Subject: [PATCH 19/26] feat: create financial year when not found --- .../AddFinancialYear/AddFinancialYearCommand.cs | 2 +- .../AddFinancialYear/AddFinancialYearHandler.cs | 6 +++--- .../AddCreditTransaction/AddCreditTransactionHandler.cs | 5 +++-- .../AddDebitTransaction/AddDebitTransactionHandler.cs | 5 +++-- .../EditTransaction/EditTransactionHandler.cs | 9 +++++---- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs index 2c2ba878..8beea30d 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearCommand.cs @@ -2,7 +2,7 @@ namespace Commands.Handlers.FinancialYear.AddFinancialYear; -public record AddFinancialYearCommand() : IRequest; +public record AddFinancialYearCommand() : IRequest; public class AddFinancialYearCommandValidator : AbstractValidator diff --git a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs index 13f5cad2..6ec525ae 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs @@ -5,7 +5,7 @@ namespace Commands.Handlers.FinancialYear.AddFinancialYear; -public class AddFinancialYearHandler : IRequestHandler +public class AddFinancialYearHandler : IRequestHandler { private readonly IFinancialYearRepository _financialYearRepository; private readonly IFinancialYearConfigurationRepository _financialYearConfigurationRepository; @@ -20,7 +20,7 @@ public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository, _financialYearConfigurationRepository = financialYearConfigurationRepository; _haSpManContext = haSpManContext; } - public async Task Handle(AddFinancialYearCommand request, CancellationToken cancellationToken) + public async Task Handle(AddFinancialYearCommand request, CancellationToken cancellationToken) { var configuration = await _financialYearConfigurationRepository.Get(cancellationToken) ?? throw new InvalidOperationException("Financial year configuration is missing"); @@ -41,6 +41,6 @@ public async Task Handle(AddFinancialYearCommand request, CancellationToke _financialYearRepository.Add(financialYear); await _financialYearRepository.SaveChangesAsync(cancellationToken); - return financialYear.Id; + return financialYear; } } \ No newline at end of file diff --git a/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs b/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs index aaabe05c..3a590454 100644 --- a/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs +++ b/Commands/Handlers/Transaction/AddCreditTransaction/AddCreditTransactionHandler.cs @@ -1,4 +1,5 @@  +using Commands.Handlers.FinancialYear.AddFinancialYear; using Commands.Handlers.Transaction.AddAttachments; using Domain; @@ -23,8 +24,8 @@ public async Task Handle(AddCreditTransactionCommand request, Cancellation var financialYear = await _financialYearRepository.GetFinancialYearByDateAsync(request.ReceivedDateTime, cancellationToken) - ?? throw new ArgumentException($"No financial year found for date {request.ReceivedDateTime}", nameof(request.ReceivedDateTime)); - + ?? await _mediator.Send(new AddFinancialYearCommand(), cancellationToken); + var totalAmount = request.TransactionTypeAmounts.Sum(x => x.Amount); var transaction = new CreditTransaction(request.CounterPartyName, request.BankAccountId, totalAmount, request.ReceivedDateTime, request.Description, new List(), request.MemberId, diff --git a/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs b/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs index 7de8ddc3..7a9a0c02 100644 --- a/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs +++ b/Commands/Handlers/Transaction/AddDebitTransaction/AddDebitTransactionHandler.cs @@ -1,4 +1,5 @@ -using Commands.Handlers.Transaction.AddAttachments; +using Commands.Handlers.FinancialYear.AddFinancialYear; +using Commands.Handlers.Transaction.AddAttachments; using Domain; using Domain.Interfaces; @@ -19,7 +20,7 @@ public async Task Handle(AddDebitTransactionCommand request, CancellationT { var financialYear = await _financialYearRepository.GetFinancialYearByDateAsync(request.ReceivedDateTime, cancellationToken) - ?? throw new ArgumentException($"No financial year found for date {request.ReceivedDateTime}", nameof(request.ReceivedDateTime)); + ?? await _mediator.Send(new AddFinancialYearCommand(), cancellationToken); var totalAmount = request.TransactionTypeAmounts.Sum(x => x.Amount); var transaction = new DebitTransaction(request.CounterPartyName, request.BankAccountId, totalAmount, diff --git a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs index 4ed54378..131258d0 100644 --- a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs +++ b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs @@ -1,4 +1,5 @@ -using Commands.Handlers.Transaction.AddAttachments; +using Commands.Handlers.FinancialYear.AddFinancialYear; +using Commands.Handlers.Transaction.AddAttachments; using Domain.Interfaces; @@ -21,9 +22,9 @@ public async Task Handle(EditTransactionCommand request, CancellationToken cance { var financialYear = await _financialYearRepository.GetFinancialYearByTransactionId(request.Id, cancellationToken) - ?? throw new ArgumentException($"No financial year found for date {request.ReceivedDateTime}", nameof(request.ReceivedDateTime)); - - if(financialYear.StartDate >= request.ReceivedDateTime && + ?? await _mediator.Send(new AddFinancialYearCommand(), cancellationToken); + + if (financialYear.StartDate >= request.ReceivedDateTime && financialYear.EndDate <= request.ReceivedDateTime) { financialYear.ChangeTransaction(request.Id, request.CounterPartyName, request.MemberId, request.BankAccountId, From 3a05175990273d4faa7aeec7b3c613c413631fbe Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 21:53:59 +0200 Subject: [PATCH 20/26] feat: use options pattern for financial year config --- .../When_adding_a_financial_year.cs | 19 +++++------- .../AddFinancialYearHandler.cs | 16 +++++----- .../SetFinancialYearConfigurationCommand.cs | 20 ------------- .../SetFinancialYearConfigurationHandler.cs | 18 ----------- Domain/FinancialYearConfiguration.cs | 17 ++--------- Domain/Interfaces/IFinancialYearRepository.cs | 7 ----- ...FinancialYearConfigurationConfiguration.cs | 14 --------- Persistence/HaSpManContext.cs | 4 +-- ...84205_TransactionsBelongToFinancialYear.cs | 2 +- .../FinancialYearConfigurationRepository.cs | 30 ------------------- .../Repositories/FinancialYearRepository.cs | 1 + Web/Startup.cs | 3 +- Web/appsettings.json | 6 +++- 13 files changed, 27 insertions(+), 130 deletions(-) delete mode 100644 Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs delete mode 100644 Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs delete mode 100644 Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs delete mode 100644 Persistence/Repositories/FinancialYearConfigurationRepository.cs diff --git a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs index afe1d0e5..423eee72 100644 --- a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs @@ -4,6 +4,7 @@ using Domain.Interfaces; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; using Moq; @@ -18,16 +19,16 @@ public class When_adding_a_financial_year { private readonly Mock _financialYearRepositoryMock; private readonly HaSpManContext _haspmanDbContext; - private readonly Mock _financialYearConfigurationMock; public When_adding_a_financial_year() { _financialYearRepositoryMock = new Mock(); - _financialYearConfigurationMock = new Mock(); - _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() + var financialYearConfigurationOptions = Options.Create(new FinancialYearConfiguration()); + + _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); - SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, _financialYearConfigurationMock.Object, + SUT = new AddFinancialYearHandler(_financialYearRepositoryMock.Object, financialYearConfigurationOptions, _haspmanDbContext); } @@ -51,10 +52,7 @@ public async Task It_should_add_a_new_financial_year() .Setup(x => x.Add(It.Is(financialYear => financialYear.EndDate == endDate))) .Verifiable(); - - _financialYearConfigurationMock.Setup(x => x.Get(It.IsAny())) - .ReturnsAsync(new FinancialYearConfiguration(new DateTimeOffset(new DateTime(2021, 9, 1)))); - + await SUT.Handle(new AddFinancialYearCommand(), CancellationToken.None); _financialYearRepositoryMock @@ -90,10 +88,7 @@ public async Task It_should_add_a_new_financial_year_following_the_last_year() .Setup(x => x.Add(It.Is(financialYear => financialYear.EndDate == endDate))) .Verifiable(); - - _financialYearConfigurationMock.Setup(x => x.Get(It.IsAny())) - .ReturnsAsync(new FinancialYearConfiguration(new DateTimeOffset(new DateTime(2021, 9, 1)))); - + await SUT.Handle(new AddFinancialYearCommand(), CancellationToken.None); _financialYearRepositoryMock diff --git a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs index 6ec525ae..ae40728d 100644 --- a/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs +++ b/Commands/Handlers/FinancialYear/AddFinancialYear/AddFinancialYearHandler.cs @@ -1,4 +1,7 @@ -using Domain.Interfaces; +using Domain; +using Domain.Interfaces; + +using Microsoft.Extensions.Options; using Persistence; using Persistence.Repositories; @@ -8,23 +11,20 @@ namespace Commands.Handlers.FinancialYear.AddFinancialYear; public class AddFinancialYearHandler : IRequestHandler { private readonly IFinancialYearRepository _financialYearRepository; - private readonly IFinancialYearConfigurationRepository _financialYearConfigurationRepository; + private readonly FinancialYearConfiguration _financialYearOptions; private readonly HaSpManContext _haSpManContext; public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository, - IFinancialYearConfigurationRepository financialYearConfigurationRepository, + IOptions financialYearOptions, HaSpManContext haSpManContext) { _financialYearRepository = financialYearRepository; - _financialYearConfigurationRepository = financialYearConfigurationRepository; + _financialYearOptions = financialYearOptions.Value; _haSpManContext = haSpManContext; } public async Task 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); @@ -32,7 +32,7 @@ public AddFinancialYearHandler(IFinancialYearRepository financialYearRepository, // 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 startDate = new DateTimeOffset(new DateTime(year, _financialYearOptions.StartDate.Month, _financialYearOptions.StartDate.Day)); var financialYear = new Domain.FinancialYear( startDate, diff --git a/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs b/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs deleted file mode 100644 index 2cac9747..00000000 --- a/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationCommand.cs +++ /dev/null @@ -1,20 +0,0 @@ -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 -{ - public SetFinancialYearConfigurationCommandValidator() - { - RuleFor(x => x.StartDate).NotEmpty(); - } -} \ No newline at end of file diff --git a/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs b/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs deleted file mode 100644 index f1228d1b..00000000 --- a/Commands/Handlers/FinancialYearConfiguration/SetFinancialYearConfigurationHandler.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Domain.Interfaces; - -namespace Commands.Handlers.FinancialYearConfiguration; - -public class SetFinancialYearConfigurationHandler : IRequestHandler -{ - private readonly IFinancialYearConfigurationRepository _financialYearConfigurationRepository; - - public SetFinancialYearConfigurationHandler(IFinancialYearConfigurationRepository financialYearConfigurationRepository) - { - _financialYearConfigurationRepository = financialYearConfigurationRepository; - } - public async Task Handle(SetFinancialYearConfigurationCommand request, CancellationToken cancellationToken) - { - _financialYearConfigurationRepository.Set(new Domain.FinancialYearConfiguration(request.StartDate)); - await _financialYearConfigurationRepository.SaveChangesAsync(cancellationToken); - } -} \ No newline at end of file diff --git a/Domain/FinancialYearConfiguration.cs b/Domain/FinancialYearConfiguration.cs index e05f6f82..37b693e4 100644 --- a/Domain/FinancialYearConfiguration.cs +++ b/Domain/FinancialYearConfiguration.cs @@ -2,19 +2,6 @@ public class FinancialYearConfiguration { -#pragma warning disable CS8618 - public FinancialYearConfiguration() - { - - }// Make EFCore happy -#pragma warning restore CS8618 - - public FinancialYearConfiguration(DateTimeOffset startDate) - { - StartDate = startDate; - } - public Guid Id { get; set; } - public DateTimeOffset StartDate { get; private set; } - - public TimeSpan Duration = TimeSpan.FromDays(365); + public DateTimeOffset StartDate { get; set; } + public DateTimeOffset EndDate { get; set;} } \ No newline at end of file diff --git a/Domain/Interfaces/IFinancialYearRepository.cs b/Domain/Interfaces/IFinancialYearRepository.cs index d28f6381..9153806e 100644 --- a/Domain/Interfaces/IFinancialYearRepository.cs +++ b/Domain/Interfaces/IFinancialYearRepository.cs @@ -1,12 +1,5 @@ namespace Domain.Interfaces; -public interface IFinancialYearConfigurationRepository -{ - Task Get(CancellationToken cancellationToken); - void Set(FinancialYearConfiguration financialYearConfiguration); - Task SaveChangesAsync(CancellationToken cancellationToken); -} - public interface IFinancialYearRepository { Task GetByIdAsync(Guid id, CancellationToken cancellationToken); diff --git a/Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs deleted file mode 100644 index dbedfcab..00000000 --- a/Persistence/EntityConfigurations/FinancialYearConfigurationConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Domain; - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Persistence.EntityConfigurations; - -public class FinancialYearConfigurationConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.Property(x => x.StartDate).IsRequired(); - } -} \ No newline at end of file diff --git a/Persistence/HaSpManContext.cs b/Persistence/HaSpManContext.cs index 52cd5c24..72ba52f4 100644 --- a/Persistence/HaSpManContext.cs +++ b/Persistence/HaSpManContext.cs @@ -23,9 +23,7 @@ public HaSpManContext(DbContextOptions options) public DbSet BankAccountsWithTotals { get; set; } = null!; public DbSet FinancialYears { get;set; } = null!; - - public DbSet FinancialYearConfigurations { get; set; } = null!; - + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable("__EFMigrationsHistory", Schema.HaSpMan)); diff --git a/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs b/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs index 5e239fb8..50bfb36a 100644 --- a/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs +++ b/Persistence/Migrations/20230728184205_TransactionsBelongToFinancialYear.cs @@ -12,7 +12,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.Sql("INSERT INTO " + "HaspMan.FinancialYears (Id, StartDate, EndDate, IsClosed) " + - "VALUES (newId(), CAST('2022-09-01 00:00:00.0000000 +01:00' AS DATETIMEOFFSET),CAST('2023-08-31 00:00:00.0000000 +01:00' AS DATETIMEOFFSET), 0)"); + "VALUES (newId(), CAST('2022-09-01 00:00:00.0000000 +02:00' AS DATETIMEOFFSET),CAST('2023-08-31 00:00:00.0000000 +02:00' AS DATETIMEOFFSET), 0)"); migrationBuilder.DropIndex("IX_Transactions_FinancialYearId", schema: "HaspMan", table: "Transactions"); migrationBuilder.AlterColumn( diff --git a/Persistence/Repositories/FinancialYearConfigurationRepository.cs b/Persistence/Repositories/FinancialYearConfigurationRepository.cs deleted file mode 100644 index d475aa3b..00000000 --- a/Persistence/Repositories/FinancialYearConfigurationRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Domain; -using Domain.Interfaces; - -using Microsoft.EntityFrameworkCore; - -namespace Persistence.Repositories; - -public class FinancialYearConfigurationRepository : IFinancialYearConfigurationRepository -{ - private readonly HaSpManContext _dbContext; - - public FinancialYearConfigurationRepository(HaSpManContext dbContext) - { - _dbContext = dbContext; - } - public Task Get(CancellationToken cancellationToken) - { - return _dbContext.FinancialYearConfigurations.SingleOrDefaultAsync(cancellationToken); - } - - public void Set(FinancialYearConfiguration financialYearConfiguration) - { - _dbContext.FinancialYearConfigurations.Update(financialYearConfiguration); - } - - public async Task SaveChangesAsync(CancellationToken cancellationToken) - { - await _dbContext.SaveChangesAsync(cancellationToken); - } -} \ No newline at end of file diff --git a/Persistence/Repositories/FinancialYearRepository.cs b/Persistence/Repositories/FinancialYearRepository.cs index 8abb0e3c..91892901 100644 --- a/Persistence/Repositories/FinancialYearRepository.cs +++ b/Persistence/Repositories/FinancialYearRepository.cs @@ -2,6 +2,7 @@ using Domain.Interfaces; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; namespace Persistence.Repositories; diff --git a/Web/Startup.cs b/Web/Startup.cs index bdd14a0b..3a3c55b0 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -1,6 +1,7 @@ using Commands.Handlers.Transaction.AddDebitTransaction; using Commands.Services; +using Domain; using Domain.Interfaces; using FluentValidation; @@ -52,11 +53,11 @@ public void ConfigureServices(IServiceCollection services) DbContextExtensions.MigrateHaSpManContext(dbConnectionString); services.Configure(Configuration.GetSection("Storage")); + services.Configure(Configuration.GetSection("FinancialYear")); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddAutoMapper( typeof(MapperProfiles.MemberProfile), diff --git a/Web/appsettings.json b/Web/appsettings.json index d9d9a9bf..0e51e7df 100644 --- a/Web/appsettings.json +++ b/Web/appsettings.json @@ -6,5 +6,9 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "FinancialYear": { + "StartDate": "2022-09-01", + "EndDate": "2023-08-31" + } } From 63251562f676f32d85057bff2f43e515c51f4945 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 22:39:20 +0200 Subject: [PATCH 21/26] feat: adds page with list of financial years --- Queries/BankAccounts/GetBankAccountById.cs | 2 +- Queries/BankAccounts/SearchBankAccounts.cs | 2 +- .../FinancialYears/GetFinancialYearsQuery.cs | 38 +++++++ .../CloseFinancialYearDialog.razor | 32 ++++++ Web/Pages/FinancialYears/FinancialYears.razor | 103 ++++++++++++++++++ Web/Shared/NavMenu.razor | 7 +- Web/Web.csproj | 3 +- 7 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 Queries/FinancialYears/GetFinancialYearsQuery.cs create mode 100644 Web/Pages/FinancialYears/CloseFinancialYearDialog.razor create mode 100644 Web/Pages/FinancialYears/FinancialYears.razor diff --git a/Queries/BankAccounts/GetBankAccountById.cs b/Queries/BankAccounts/GetBankAccountById.cs index 0e4912e2..fa6710f6 100644 --- a/Queries/BankAccounts/GetBankAccountById.cs +++ b/Queries/BankAccounts/GetBankAccountById.cs @@ -19,7 +19,7 @@ public GetBankAccountByIdHandler(IMapper mapper, IDbContextFactory Handle(GetBankAccountByIdQuery request, CancellationToken cancellationToken) { - var context = _contextFactory.CreateDbContext(); + var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var bankAccount = await context.BankAccounts.SingleAsync(b => b.Id == request.Id, cancellationToken: cancellationToken); return _mapper.Map(bankAccount); diff --git a/Queries/BankAccounts/SearchBankAccounts.cs b/Queries/BankAccounts/SearchBankAccounts.cs index 66392d6e..86c1782b 100644 --- a/Queries/BankAccounts/SearchBankAccounts.cs +++ b/Queries/BankAccounts/SearchBankAccounts.cs @@ -38,7 +38,7 @@ public SearchBankAccountsHandler(IDbContextFactory contextFactor public async Task> Handle(SearchBankAccountsQuery request, CancellationToken cancellationToken) { - var context = _contextFactory.CreateDbContext(); + var context = await _contextFactory.CreateDbContextAsync(cancellationToken); var bankAccountsQueryable = context.BankAccountsWithTotals .AsNoTracking() .Where(GetFilterCriteria(request.SearchString)); diff --git a/Queries/FinancialYears/GetFinancialYearsQuery.cs b/Queries/FinancialYears/GetFinancialYearsQuery.cs new file mode 100644 index 00000000..d1334d12 --- /dev/null +++ b/Queries/FinancialYears/GetFinancialYearsQuery.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; + +using Persistence; + +namespace Queries.FinancialYears; + +public record GetFinancialYearsQuery() : IRequest>; +public record FinancialYear(Guid Id, DateTimeOffset StartDateTimeOffset, DateTimeOffset EndDateTimeOffset, bool IsCloded); + + +public class GetFinancialYearsHandler : IRequestHandler> +{ + private readonly IDbContextFactory _contextFactory; + private readonly IMapper _mapper; + + public GetFinancialYearsHandler(IDbContextFactory contextFactory, IMapper mapper) + { + _contextFactory = contextFactory; + _mapper = mapper; + } + public async Task> Handle(GetFinancialYearsQuery request, + CancellationToken cancellationToken) + { + var context = await _contextFactory.CreateDbContextAsync(cancellationToken); + var financialYears = await context.FinancialYears.ToListAsync(cancellationToken); + + return financialYears + .OrderByDescending(x => x.StartDate) + .Select(x => new FinancialYear(x.Id, x.StartDate, x.EndDate, x.IsClosed)) + .ToList(); + } +} \ No newline at end of file diff --git a/Web/Pages/FinancialYears/CloseFinancialYearDialog.razor b/Web/Pages/FinancialYears/CloseFinancialYearDialog.razor new file mode 100644 index 00000000..d77c6af7 --- /dev/null +++ b/Web/Pages/FinancialYears/CloseFinancialYearDialog.razor @@ -0,0 +1,32 @@ +@using Commands.Handlers.FinancialYear.CloseFinancialYear +@using Queries.FinancialYears + +@inject IMediator _mediatr + + + + Are you sure you want to close financial year @FinancialYear.StartDateTimeOffset.Year - @FinancialYear.EndDateTimeOffset.Year? + + + Cancel + Ok + + + +@code { + + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } = null!; + + [Parameter] + public FinancialYear FinancialYear { get; set; } = null!; + + private async Task Submit() + { + + await _mediatr.Send(new CloseFinancialYearCommand(FinancialYear.Id)); + MudDialog.Close(true); + } + + void Cancel() => MudDialog.Cancel(); +} \ No newline at end of file diff --git a/Web/Pages/FinancialYears/FinancialYears.razor b/Web/Pages/FinancialYears/FinancialYears.razor new file mode 100644 index 00000000..0d0c226f --- /dev/null +++ b/Web/Pages/FinancialYears/FinancialYears.razor @@ -0,0 +1,103 @@ +@page "/financialyears" +@attribute [Authorize] +@using Queries.FinancialYears +@using Commands.Handlers.FinancialYear.CloseFinancialYear + +@inject IMediator _mediator +@inject ISnackbar Snackbar +@inject IDialogService DialogService + + + + Financial years + + + + + + Start date + + + End date + + + Is closed + + + + + + + @context.StartDateTimeOffset.Date.ToString("dd-MM-yyyy") + @context.EndDateTimeOffset.Date.ToString("dd-MM-yyyy") + @context.IsCloded + + + + + + + + + + + + + + No matching records found + + + Loading... + + + + + + +@code { + private MudTable? table; + + private async Task> ServerReload(TableState state) + { + StateHasChanged(); + + var query = new GetFinancialYearsQuery(); + var data = await _mediator.Send(query); + + return new TableData() + { + TotalItems = data.Count, + Items = data + }; + } + + private void OnSearch(string text) + { + table?.ReloadServerData(); + } + + private async Task CloseFinancialYear(FinancialYear financialYear) + { + + var options = new DialogOptions { }; + var parameters = new DialogParameters() + { + ["financialYear"] = financialYear + }; + var reference = await DialogService.ShowAsync($"Close financial year", parameters, options); + var dialog = await reference.Result; + if (!dialog.Canceled) + { + + Snackbar.Clear(); + Snackbar.Add($"Closed financial year {financialYear.StartDateTimeOffset.Year} - {financialYear.EndDateTimeOffset.Year}"); + table?.ReloadServerData(); + } + } + +} \ No newline at end of file diff --git a/Web/Shared/NavMenu.razor b/Web/Shared/NavMenu.razor index b5184970..700d7cd4 100644 --- a/Web/Shared/NavMenu.razor +++ b/Web/Shared/NavMenu.razor @@ -8,8 +8,11 @@ New transaction - List bankaccounts - New bankaccount + List bankaccounts + New bankaccount + + + List financial years About \ No newline at end of file diff --git a/Web/Web.csproj b/Web/Web.csproj index cbb96e27..beba806a 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -14,8 +14,7 @@ all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 882159479174ecbc249d2e102f63dc22126915a8 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 22:45:35 +0200 Subject: [PATCH 22/26] test: fix unit tests --- Commands.Test/FinancialYear/When_adding_a_financial_year.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs index 423eee72..13dc7620 100644 --- a/Commands.Test/FinancialYear/When_adding_a_financial_year.cs +++ b/Commands.Test/FinancialYear/When_adding_a_financial_year.cs @@ -24,7 +24,11 @@ public When_adding_a_financial_year() { _financialYearRepositoryMock = new Mock(); - var financialYearConfigurationOptions = Options.Create(new FinancialYearConfiguration()); + var financialYearConfigurationOptions = Options.Create(new FinancialYearConfiguration() + { + StartDate = new DateTimeOffset(new DateTime(2022,9,1)), + EndDate = new DateTimeOffset(new DateTime(2023,8,31)) + }); _haspmanDbContext = new HaSpManContext(new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); From ef0719778cd7a512488ce6806abfd82fe0aaafd4 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 23:36:01 +0200 Subject: [PATCH 23/26] feat: move transaction to correct year if date changed --- .../EditTransaction/EditTransactionHandler.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs index 131258d0..899946bd 100644 --- a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs +++ b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs @@ -22,7 +22,7 @@ public async Task Handle(EditTransactionCommand request, CancellationToken cance { var financialYear = await _financialYearRepository.GetFinancialYearByTransactionId(request.Id, cancellationToken) - ?? await _mediator.Send(new AddFinancialYearCommand(), cancellationToken); + ?? throw new ArgumentException($"No transaction found for Id {request.Id}", nameof(request.Id)); if (financialYear.StartDate >= request.ReceivedDateTime && financialYear.EndDate <= request.ReceivedDateTime) @@ -33,7 +33,15 @@ await _financialYearRepository.GetFinancialYearByTransactionId(request.Id, cance else { var transaction = financialYear.Transactions.First(x => x.Id == request.Id); - financialYear.AddTransaction(transaction); + + var matchingFinancialYear = + await _financialYearRepository.GetFinancialYearByDateAsync(request.ReceivedDateTime, + cancellationToken) + ?? await _mediator.Send(new AddFinancialYearCommand(), cancellationToken); + + matchingFinancialYear.AddTransaction(transaction); + matchingFinancialYear.ChangeTransaction(request.Id, request.CounterPartyName, request.MemberId, request.BankAccountId, + request.ReceivedDateTime, request.TransactionTypeAmounts, request.Description); financialYear.Transactions.Remove(transaction); } From e9b27f216063222deee59b82ad81d3758bfb7c26 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Fri, 28 Jul 2023 23:50:58 +0200 Subject: [PATCH 24/26] feat: remove IsRequired and FinancialYearConfig --- .../FinancialYearEntityTypeConfiguration.cs | 6 +- ...0_RemoveFinancialConfiguration.Designer.cs | 384 ++++++++++++++++++ ...0728215040_RemoveFinancialConfiguration.cs | 36 ++ .../Migrations/HaSpManContextModelSnapshot.cs | 14 - 4 files changed, 423 insertions(+), 17 deletions(-) create mode 100644 Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.Designer.cs create mode 100644 Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.cs diff --git a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs index 7c74a321..517930e2 100644 --- a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs +++ b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs @@ -9,9 +9,9 @@ public class FinancialYearEntityTypeConfiguration : IEntityTypeConfiguration builder) { - builder.Property(x => x.StartDate).IsRequired(); - builder.Property(x => x.EndDate).IsRequired(); - builder.Property(x => x.IsClosed).IsRequired(); + builder.Property(x => x.StartDate); + builder.Property(x => x.EndDate); + builder.Property(x => x.IsClosed); // Owned entity types cannot have inheritance hierarchies https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities#current-shortcomings builder.HasMany(x => x.Transactions).WithOne(); builder.Navigation(x => x.Transactions).AutoInclude(); diff --git a/Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.Designer.cs b/Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.Designer.cs new file mode 100644 index 00000000..45278d87 --- /dev/null +++ b/Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.Designer.cs @@ -0,0 +1,384 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Persistence; + +#nullable disable + +namespace Persistence.Migrations +{ + [DbContext(typeof(HaSpManContext))] + [Migration("20230728215040_RemoveFinancialConfiguration")] + partial class RemoveFinancialConfiguration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("HaSpMan") + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccountNumber") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar"); + + b.HasKey("Id"); + + b.ToTable("BankAccounts", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetimeoffset"); + + b.Property("IsClosed") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("FinancialYears", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.Property("MembershipExpiryDate") + .HasColumnType("datetimeoffset"); + + b.Property("MembershipFee") + .HasColumnType("float"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("Members", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("CounterPartyName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("DateFiled") + .HasColumnType("datetimeoffset"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FinancialYearId") + .HasColumnType("uniqueidentifier"); + + b.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReceivedDateTime") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("FinancialYearId"); + + b.ToTable("Transactions", "HaSpMan"); + + b.HasDiscriminator("Discriminator").HasValue("Transaction"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b.Property("NumberOfTransactions") + .HasColumnType("bigint"); + + b.Property("Total") + .HasColumnType("decimal(18,2)"); + + b.HasKey("BankAccountId"); + + b.ToTable((string)null); + + b.ToView("vwBankAccountTotals", "HaSpMan"); + }); + + modelBuilder.Entity("Domain.CreditTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("CreditTransaction"); + }); + + modelBuilder.Entity("Domain.DebitTransaction", b => + { + b.HasBaseType("Domain.Transaction"); + + b.HasDiscriminator().HasValue("DebitTransaction"); + }); + + modelBuilder.Entity("Domain.BankAccount", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("BankAccountId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("BankAccountId", "Id"); + + b1.ToTable("BankAccount_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("BankAccountId"); + }); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Member", b => + { + b.OwnsMany("Types.AuditEvent", "AuditEvents", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar"); + + b1.Property("PerformedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar"); + + b1.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b1.HasKey("MemberId", "Id"); + + b1.ToTable("Member_AuditEvents", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.OwnsOne("Types.Address", "Address", b1 => + { + b1.Property("MemberId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar"); + + b1.Property("HouseNumber") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("varchar"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar"); + + b1.HasKey("MemberId"); + + b1.ToTable("Members", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("MemberId"); + }); + + b.Navigation("Address") + .IsRequired(); + + b.Navigation("AuditEvents"); + }); + + modelBuilder.Entity("Domain.Transaction", b => + { + b.HasOne("Domain.FinancialYear", null) + .WithMany("Transactions") + .HasForeignKey("FinancialYearId"); + + b.OwnsMany("Domain.TransactionAttachment", "Attachments", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("FullPath") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_Attachments", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.OwnsMany("Domain.TransactionTypeAmount", "TransactionTypeAmounts", b1 => + { + b1.Property("TransactionId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b1.Property("TransactionType") + .HasColumnType("int"); + + b1.HasKey("TransactionId", "Id"); + + b1.ToTable("Transaction_TransactionTypeAmounts", "HaSpMan"); + + b1.WithOwner() + .HasForeignKey("TransactionId"); + }); + + b.Navigation("Attachments"); + + b.Navigation("TransactionTypeAmounts"); + }); + + modelBuilder.Entity("Persistence.Views.BankAccountsWithTotals", b => + { + b.HasOne("Domain.BankAccount", "Account") + .WithOne() + .HasForeignKey("Persistence.Views.BankAccountsWithTotals", "BankAccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Domain.FinancialYear", b => + { + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.cs b/Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.cs new file mode 100644 index 00000000..de1ac50a --- /dev/null +++ b/Persistence/Migrations/20230728215040_RemoveFinancialConfiguration.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Migrations +{ + /// + public partial class RemoveFinancialConfiguration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FinancialYearConfigurations", + schema: "HaSpMan"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "FinancialYearConfigurations", + schema: "HaSpMan", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + StartDate = table.Column(type: "datetimeoffset", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FinancialYearConfigurations", x => x.Id); + }); + } + } +} diff --git a/Persistence/Migrations/HaSpManContextModelSnapshot.cs b/Persistence/Migrations/HaSpManContextModelSnapshot.cs index c0fca612..94619d0f 100644 --- a/Persistence/Migrations/HaSpManContextModelSnapshot.cs +++ b/Persistence/Migrations/HaSpManContextModelSnapshot.cs @@ -64,20 +64,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("FinancialYears", "HaSpMan"); }); - modelBuilder.Entity("Domain.FinancialYearConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("StartDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("FinancialYearConfigurations", "HaSpMan"); - }); - modelBuilder.Entity("Domain.Member", b => { b.Property("Id") From 5e2d7e07807d0b67a4a2e9b878a7c8b2562c6751 Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 29 Jul 2023 00:16:30 +0200 Subject: [PATCH 25/26] feat: fix flow for changing transactions --- Commands.Test/Transaction/When_editing_a_transaction.cs | 2 +- .../Transaction/EditTransaction/EditTransactionHandler.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Commands.Test/Transaction/When_editing_a_transaction.cs b/Commands.Test/Transaction/When_editing_a_transaction.cs index 15d3ea89..4a8ddd95 100644 --- a/Commands.Test/Transaction/When_editing_a_transaction.cs +++ b/Commands.Test/Transaction/When_editing_a_transaction.cs @@ -57,6 +57,6 @@ public async Task It_should_throw_an_exception_when_locked() DateTimeOffset.Now, "Another description", new List(), new List()), CancellationToken.None)); - exception.Message.Should().Be("Financial year is closed"); + exception.Message.Should().Be("Financial year is already closed"); } } diff --git a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs index 899946bd..ae603092 100644 --- a/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs +++ b/Commands/Handlers/Transaction/EditTransaction/EditTransactionHandler.cs @@ -24,8 +24,8 @@ public async Task Handle(EditTransactionCommand request, CancellationToken cance await _financialYearRepository.GetFinancialYearByTransactionId(request.Id, cancellationToken) ?? throw new ArgumentException($"No transaction found for Id {request.Id}", nameof(request.Id)); - if (financialYear.StartDate >= request.ReceivedDateTime && - financialYear.EndDate <= request.ReceivedDateTime) + if (financialYear.StartDate <= request.ReceivedDateTime && + financialYear.EndDate >= request.ReceivedDateTime) { financialYear.ChangeTransaction(request.Id, request.CounterPartyName, request.MemberId, request.BankAccountId, request.ReceivedDateTime, request.TransactionTypeAmounts, request.Description); From 8573cd7601bbcfbdfc102fce67b271b6ea32d30f Mon Sep 17 00:00:00 2001 From: Berend Wouters Date: Sat, 29 Jul 2023 00:20:42 +0200 Subject: [PATCH 26/26] chore: remove unused config --- .../FinancialYearEntityTypeConfiguration.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs index 517930e2..91e2a0a6 100644 --- a/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs +++ b/Persistence/EntityConfigurations/FinancialYearEntityTypeConfiguration.cs @@ -9,11 +9,7 @@ public class FinancialYearEntityTypeConfiguration : IEntityTypeConfiguration builder) { - builder.Property(x => x.StartDate); - builder.Property(x => x.EndDate); - builder.Property(x => x.IsClosed); // Owned entity types cannot have inheritance hierarchies https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities#current-shortcomings builder.HasMany(x => x.Transactions).WithOne(); - builder.Navigation(x => x.Transactions).AutoInclude(); } } \ No newline at end of file