Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/categorize data #36

Merged
merged 6 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Application/Interfaces/Repositories/IAccountRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public interface IAccountRepository
Task<Account?> GetByIdAsync(long accountId);
Task<List<Account>> GetAllAccounts();
Task<List<long>> GetAllIdsAsync();
Task<List<Account>> GetByFileIdAsync(long fileId);
Task DeleteByFileIdAsync(long fileId);
}
11 changes: 11 additions & 0 deletions src/Application/Interfaces/Repositories/IFileIdRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Domain.Entities;

namespace Application.Interfaces.Repositories;

public interface IFileIdRepository
{
Task<bool> IdExistsAsync(long fileId);
Task AddAsync(FileId fileId);
Task DeleteByIdAsync(long fileId);
Task<List<FileId>> GetAllIdsAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface ITransactionRepository
Task<List<Transaction>> GetBySourceAccountId(long accountId);
Task<List<Transaction>> GetByDestinationAccountId(long accountId);
Task<List<long>> GetAllIdsAsync();
Task<List<Transaction>> GetByFileIdAsync(long fileId);
Task DeleteByFileIdAsync(long fileId);
}
4 changes: 3 additions & 1 deletion src/Application/Interfaces/Services/IAccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ namespace Application.Interfaces.Services;

public interface IAccountService
{
Task<Result> AddAccountsFromCsvAsync(string filePath);
Task<Result> AddAccountsFromCsvAsync(string filePath, long fileId);
Task<Result<Account>> GetAccountByIdAsync(long accountId);
Task<Result<List<Account>>> GetAllAccountsAsync();
Task<Result<List<Account>>> GetAccountsByFileIdAsync(long fileId);
Task<Result> DeleteAccountsByFileIdAsync(long fileId);
}
4 changes: 3 additions & 1 deletion src/Application/Interfaces/Services/ITransactionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ namespace Application.Interfaces.Services;

public interface ITransactionService
{
Task<Result> AddTransactionsFromCsvAsync(string filePath);
Task<Result> AddTransactionsFromCsvAsync(string filePath, long fileId);
Task<Result<List<Transaction>>> GetAllTransactionsAsync();
Task<Result<List<GetTransactionsByAccountIdResponse>>> GetTransactionsByAccountIdAsync(long accountId);
Task<Result<List<Transaction>>> GetTransactionsByFileIdAsync(long fileId);
Task<Result> DeleteTransactionsByFileIdAsync(long fileId);
}
5 changes: 3 additions & 2 deletions src/Application/Mappers/AccountMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Application.Mappers;

public static class AccountMapper
{
public static Account ToAccount(this AccountCsvModel csvModel)
public static Account ToAccount(this AccountCsvModel csvModel, long fileId)
{
return new Account
{
Expand All @@ -18,7 +18,8 @@ public static Account ToAccount(this AccountCsvModel csvModel)
BranchName = csvModel.BranchName,
OwnerName = csvModel.OwnerName,
OwnerLastName = csvModel.OwnerLastName,
OwnerId = csvModel.OwnerId
OwnerId = csvModel.OwnerId,
FileId = fileId
};
}
}
5 changes: 3 additions & 2 deletions src/Application/Mappers/TransactionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Application.Mappers;
public static class TransactionMapper
{
public static Transaction ToTransaction(this TransactionCsvModel csvModel)
public static Transaction ToTransaction(this TransactionCsvModel csvModel, long fileId)
{
var date = DateOnly.Parse(csvModel.Date).ToDateTime(TimeOnly.Parse(csvModel.Time));
var utcDate = DateTime.SpecifyKind(date, DateTimeKind.Utc);
Expand All @@ -16,7 +16,8 @@ public static Transaction ToTransaction(this TransactionCsvModel csvModel)
Amount = csvModel.Amount,
Date = utcDate,
Type = csvModel.Type,
TrackingId = csvModel.TrackingId
TrackingId = csvModel.TrackingId,
FileId = fileId
};
}
}
51 changes: 47 additions & 4 deletions src/Application/Services/DomainService/AccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,34 @@ public class AccountService : IAccountService
{
private readonly IAccountRepository _accountRepository;
private readonly IFileReaderService _fileReaderService;
private readonly IFileIdRepository _fileIdRepository;

public AccountService(IAccountRepository accountRepository, IFileReaderService fileReaderService)
public AccountService(IAccountRepository accountRepository, IFileReaderService fileReaderService, IFileIdRepository fileIdRepository)
{
_accountRepository = accountRepository;
_fileReaderService = fileReaderService;
_fileIdRepository = fileIdRepository;
}

public async Task<Result> AddAccountsFromCsvAsync(string filePath)
public async Task<Result> AddAccountsFromCsvAsync(string filePath, long fileId)
{
try
{
var accountCsvModels = _fileReaderService.ReadFromFile<AccountCsvModel>(filePath);

var accounts = accountCsvModels
.Select(csvModel => csvModel.ToAccount())
.Select(csvModel => csvModel.ToAccount(fileId))
.ToList();

var existingAccountIds = await _accountRepository.GetAllIdsAsync();
var newAccounts = accounts.Where(a => !existingAccountIds.Contains(a.AccountId)).ToList();


var fileAlreadyExists = await _fileIdRepository.IdExistsAsync(fileId);
if (fileAlreadyExists)
{
return Result.Fail("File-Id already exists");
}
await _fileIdRepository.AddAsync(new FileId { Id = fileId });
await _accountRepository.CreateBulkAsync(newAccounts);
return Result.Ok();
}
Expand Down Expand Up @@ -72,4 +80,39 @@ public async Task<Result<List<Account>>> GetAllAccountsAsync()
return Result<List<Account>>.Fail($"An unexpected error occurred: {ex.Message}");
}
}

public async Task<Result<List<Account>>> GetAccountsByFileIdAsync(long fileId)
{
try
{
if (!await _fileIdRepository.IdExistsAsync(fileId))
{
return Result<List<Account>>.Fail("File-Id not found");
}
var accounts = await _accountRepository.GetByFileIdAsync(fileId);
return Result<List<Account>>.Ok(accounts);
}
catch (Exception ex)
{
return Result<List<Account>>.Fail($"An unexpected error occurred: {ex.Message}");
}
}

public async Task<Result> DeleteAccountsByFileIdAsync(long fileId)
{
try
{
if (!await _fileIdRepository.IdExistsAsync(fileId))
{
return Result<List<Account>>.Fail("File-Id not found");
}
await _accountRepository.DeleteByFileIdAsync(fileId);
await _fileIdRepository.DeleteByIdAsync(fileId);
return Result.Ok();
}
catch (Exception ex)
{
return Result.Fail($"An unexpected error occurred: {ex.Message}");
}
}
}
61 changes: 53 additions & 8 deletions src/Application/Services/DomainService/TransactionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,60 @@ public class TransactionService : ITransactionService
private readonly ITransactionRepository _transactionRepository;
private readonly IFileReaderService _fileReaderService;
private readonly IAccountRepository _accountRepository;

public TransactionService(ITransactionRepository transactionRepository, IFileReaderService fileReaderService, IAccountRepository accountRepository)
private readonly IFileIdRepository _fileIdRepository;
public TransactionService(ITransactionRepository transactionRepository,
IFileReaderService fileReaderService,
IAccountRepository accountRepository,
IFileIdRepository fileIdRepository)
{
_transactionRepository = transactionRepository;
_fileReaderService = fileReaderService;
_accountRepository = accountRepository;
_fileIdRepository = fileIdRepository;
}

private async Task<List<long>> ValidateTransactionCsvModelsAsync(List<TransactionCsvModel> transactionCsvModels)
private async Task<List<long>> ValidateTransactionCsvModelsAsync(List<TransactionCsvModel> transactionCsvModels, long fileId)
{
var invalidTransactionIds = new List<long>();
foreach (var transactionCsvModel in transactionCsvModels)
{
var sourceAccount = await _accountRepository.GetByIdAsync(transactionCsvModel.SourceAccount);
var destinationAccount = await _accountRepository.GetByIdAsync(transactionCsvModel.DestinationAccount);
bool isValidSourceAccount = (sourceAccount != null) && (sourceAccount.FileId == fileId);
bool isValidDestinationAccount = (destinationAccount != null) && (destinationAccount.FileId == fileId);
bool isValidDate = DateOnly.TryParseExact(transactionCsvModel.Date, "MM/dd/yyyy", null, System.Globalization.DateTimeStyles.None, out var date);
bool isValidTime = TimeOnly.TryParse(transactionCsvModel.Time, out var time);
if(sourceAccount == null || destinationAccount == null || !isValidDate || !isValidTime)
if(!isValidSourceAccount || !isValidDestinationAccount || !isValidDate || !isValidTime)
{
invalidTransactionIds.Add(transactionCsvModel.TransactionId);
}
}
return invalidTransactionIds;
}

public async Task<Result> AddTransactionsFromCsvAsync(string filePath)
public async Task<Result> AddTransactionsFromCsvAsync(string filePath, long fileId)
{
try
{
var transactionCsvModels = _fileReaderService.ReadFromFile<TransactionCsvModel>(filePath);
var invalidTransactionCsvModels = await ValidateTransactionCsvModelsAsync(transactionCsvModels);
var invalidTransactionCsvModels = await ValidateTransactionCsvModelsAsync(transactionCsvModels, fileId);
var transactions = transactionCsvModels
.Where(csvModel => !invalidTransactionCsvModels.Contains(csvModel.TransactionId))
.Select(csvModel => csvModel.ToTransaction())
.Select(csvModel => csvModel.ToTransaction(fileId))
.ToList();

var existingTransactionsIds = await _transactionRepository.GetAllIdsAsync();
var newTransactions = transactions.Where(t => !existingTransactionsIds.Contains(t.TransactionId)).ToList();

var fileAlreadyExists = await _fileIdRepository.IdExistsAsync(fileId);
if (!fileAlreadyExists)
{
return Result.Fail("File-Id do not exist");
}
await _transactionRepository.CreateBulkAsync(newTransactions);
return Result.Ok(invalidTransactionCsvModels.Count == 0
? "All transactions were added successfully."
: $"Some transactions were not added because of invalid data: {string.Join(", ", invalidTransactionCsvModels)}");
: $"{invalidTransactionCsvModels.Count} transactions were not added because of invalid data: {string.Join(", ", invalidTransactionCsvModels)}");
}
catch (Exception ex)
{
Expand Down Expand Up @@ -121,4 +132,38 @@ public async Task<Result<List<GetTransactionsByAccountIdResponse>>> GetTransacti
return Result<List<GetTransactionsByAccountIdResponse>>.Fail($"An error occurred: {ex.Message}");
}
}

public async Task<Result<List<Transaction>>> GetTransactionsByFileIdAsync(long fileId)
{
try
{
if (!await _fileIdRepository.IdExistsAsync(fileId))
{
return Result<List<Transaction>>.Fail("File-Id not found");
}
var transactions = await _transactionRepository.GetByFileIdAsync(fileId);
return Result<List<Transaction>>.Ok(transactions);
}
catch (Exception ex)
{
return Result<List<Transaction>>.Fail($"An error occurred: {ex.Message}");
}
}

public async Task<Result> DeleteTransactionsByFileIdAsync(long fileId)
{
try
{
if (!await _fileIdRepository.IdExistsAsync(fileId))
{
return Result.Fail("File-Id not found");
}
await _transactionRepository.DeleteByFileIdAsync(fileId);
return Result.Ok();
}
catch (Exception ex)
{
return Result.Fail($"An error occurred: {ex.Message}");
}
}
}
2 changes: 2 additions & 0 deletions src/Domain/Entities/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class Account
[MaxLength(50)]
public string OwnerLastName { get; set; } = String.Empty;
public long OwnerId { get; set; }
public long FileId { get; set; }
public FileId? File { get; set; }

public List<Transaction> SourceTransactions { get; set; } = new();
public List<Transaction> DestinationTransactions { get; set; } = new();
Expand Down
11 changes: 11 additions & 0 deletions src/Domain/Entities/FileId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Domain.Entities;

[Table("FileIds")]
public class FileId
{
[Key]
public long Id { get; set; }
}
2 changes: 2 additions & 0 deletions src/Domain/Entities/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ public class Transaction
[MaxLength(50)]
public string Type { get; set; } = String.Empty;
public long TrackingId { get; set; }
public long FileId { get; set; }
public FileId? File { get; set; }
}
13 changes: 13 additions & 0 deletions src/Infrastructure/Data/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> dbConte
{
public DbSet<Account> Accounts { get; set; }
public DbSet<Transaction> Transactions { get; set; }
public DbSet<FileId> Files { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Expand All @@ -34,5 +35,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.WithMany(a => a.DestinationTransactions)
.HasForeignKey(t => t.DestinationAccountId)
.OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<Transaction>()
.HasOne(t => t.File)
.WithMany()
.HasForeignKey(t => t.FileId)
.OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<Account>()
.HasOne(t => t.File)
.WithMany()
.HasForeignKey(t => t.FileId)
.OnDelete(DeleteBehavior.Restrict);
}
}
Loading
Loading