Skip to content
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
13 changes: 13 additions & 0 deletions EstateReportingAPI.BusinessLogic/Queries/FileImportLogQueries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using EstateReportingAPI.Models;
using MediatR;
using SimpleResults;

namespace EstateReportingAPI.BusinessLogic.Queries;

[ExcludeFromCodeCoverage]
public record FileImportLogQueries
{
public record GetFileImportLogListQuery(Guid EstateId, Guid? MerchantId, DateTime StartDate, DateTime EndDate) : IRequest<Result<List<FileImportLog>>>;
public record GetFileImportLogQuery(Guid EstateId, Guid? MerchantId, Guid FileImportLogId) : IRequest<Result<FileImportLog>>;
}
6 changes: 0 additions & 6 deletions EstateReportingAPI.BusinessLogic/Queries/SettlementQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,4 @@ namespace EstateReportingAPI.BusinessLogic.Queries;
[ExcludeFromCodeCoverage]
public record SettlementQueries {
public record TodaysSettlementQuery(Guid EstateId, DateTime ComparisonDate) : IRequest<Result<TodaysSettlement>>;
}

[ExcludeFromCodeCoverage]
public record FileImportLogQueries
{
public record GetFileImportLogListQuery(Guid EstateId, Guid? MerchantId, DateTime StartDate, DateTime EndDate) : IRequest<Result<List<FileImportLog>>>;
}
70 changes: 70 additions & 0 deletions EstateReportingAPI.BusinessLogic/ReportingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Task<Result<MerchantScheduleResponse>> GetMerchantSchedule(MerchantQueries.GetMe

Task<Result<List<FileImportLog>>> GetFileImportLogList(FileImportLogQueries.GetFileImportLogListQuery request,
CancellationToken cancellationToken);
Task<Result<FileImportLog>> GetFileImportLog(FileImportLogQueries.GetFileImportLogQuery request,
CancellationToken cancellationToken);
}

public class ReportingManager : IReportingManager {
Expand Down Expand Up @@ -1449,6 +1451,74 @@ join fl in context.FileLines on f.FileId equals fl.FileId
return Result.Success(fileImportLogs);
}

public async Task<Result<FileImportLog>> GetFileImportLog(FileImportLogQueries.GetFileImportLogQuery request,
CancellationToken cancellationToken)
{
using ResolvedDbContext<EstateManagementContext>? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString());
await using EstateManagementContext context = resolvedContext.Context;
// Build flat projection of the required data then assemble into hierarchical models in-memory
var flatQuery = from fil in context.FileImportLogs
join f in context.Files on fil.FileImportLogId equals f.FileImportLogId
join esu in context.EstateSecurityUsers on f.UserId equals esu.SecurityUserId into esuJoin
from esu in esuJoin.DefaultIfEmpty()
join m in context.Merchants on f.MerchantId equals m.MerchantId into mJoin
from m in mJoin.DefaultIfEmpty()
join fl in context.FileLines on f.FileId equals fl.FileId
where fil.FileImportLogId == request.FileImportLogId
&& (request.MerchantId == null || f.MerchantId == request.MerchantId)
select new FileImportFlatData
{
FileImportLogId = fil.FileImportLogId,
ImportLogDateTime = fil.ImportLogDateTime,
FileId = f.FileId,
FileName = f.FileLocation,
FileProfileId = f.FileProfileId,
DateTimeUploaded = f.FileReceivedDateTime,
UserId = f.UserId,
UploadedBy = esu != null ? esu.EmailAddress : null,
MerchantId = f.MerchantId,
MerchantName = m != null ? m.Name : null,
LineNumber = fl.LineNumber,
LineContents = fl.FileLineData,
LineStatus = fl.Status
};

var flatResult = await ExecuteQuerySafeToList(flatQuery, cancellationToken, "Error retrieving file import logs");

if (flatResult.IsFailed)
return ResultHelpers.CreateFailure(flatResult);

var flatItems = flatResult.Data;

var fileImportLogs = flatItems
.GroupBy(x => new { x.FileImportLogId, x.ImportLogDateTime })
.Select(g => new FileImportLog
{
FileImportLogId = g.Key.FileImportLogId,
ImportLogDateTime = g.Key.ImportLogDateTime,
FileDetailsList = g.GroupBy(f => new { f.FileId, f.FileName, f.FileProfileId, f.DateTimeUploaded, f.UserId, f.UploadedBy, f.MerchantId, f.MerchantName })
.Select(fg => new FileDetails
{
FileId = fg.Key.FileId,
FileName = fg.Key.FileName,
FileProfile = fg.Key.FileProfileId != null ? fg.Key.FileProfileId.ToString() : null,
DateTimeUploaded = fg.Key.DateTimeUploaded,
UserId = fg.Key.UserId,
UploadedBy = fg.Key.UploadedBy,
MerchantId = fg.Key.MerchantId,
MerchantName = fg.Key.MerchantName,
FileLines = fg.Select(fl => new FileLine
{
LineNumber = fl.LineNumber,
LineContents = fl.LineContents,
LineStatus = fl.LineStatus
}).ToList()
}).ToList()
}).SingleOrDefault();

return Result.Success(fileImportLogs);
}

private async Task<DatabaseProjections.SettlementGroupProjection> GetSettlementSummary(IQueryable<DatabaseProjections.ComparisonSettlementTransactionProjection> query,
CancellationToken cancellationToken) {
// Get the settleed fees summary
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using EstateReportingAPI.BusinessLogic.Queries;
using EstateReportingAPI.Models;
using MediatR;
using SimpleResults;

namespace EstateReportingAPI.BusinessLogic.RequestHandlers;

public class FileImportLogRequestHandler : IRequestHandler<FileImportLogQueries.GetFileImportLogListQuery, Result<List<FileImportLog>>>,
IRequestHandler<FileImportLogQueries.GetFileImportLogQuery, Result<FileImportLog>> {
private readonly IReportingManager Manager;
public FileImportLogRequestHandler(IReportingManager manager)
{
this.Manager = manager;
}

public async Task<Result<List<FileImportLog>>> Handle(FileImportLogQueries.GetFileImportLogListQuery request,
CancellationToken cancellationToken) {
return await this.Manager.GetFileImportLogList(request, cancellationToken);
}

public async Task<Result<FileImportLog>> Handle(FileImportLogQueries.GetFileImportLogQuery request,
CancellationToken cancellationToken)
{
return await this.Manager.GetFileImportLog(request, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,4 @@ public async Task<Result<TodaysSettlement>> Handle(SettlementQueries.TodaysSettl
CancellationToken cancellationToken) {
return await this.Manager.GetTodaysSettlement(request, cancellationToken);
}
}

public class FileImportLogRequestHandler : IRequestHandler<FileImportLogQueries.GetFileImportLogListQuery, Result<List<FileImportLog>>>
{
private readonly IReportingManager Manager;
public FileImportLogRequestHandler(IReportingManager manager)
{
this.Manager = manager;
}

public async Task<Result<List<FileImportLog>>> Handle(FileImportLogQueries.GetFileImportLogListQuery request,
CancellationToken cancellationToken) {
return await this.Manager.GetFileImportLogList(request, cancellationToken);
}
}
104 changes: 104 additions & 0 deletions EstateReportingAPI.IntegrationTests/FileImportLogsEndpointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using EstateReportingAPI.DataTransferObjects;
Expand Down Expand Up @@ -36,6 +38,108 @@ public async Task FileImportEndpoint_GetFileImportLogs_NoData_ReturnsEmptyList()
list.Count.ShouldBe(0);
}

[Fact]
public async Task FileImportEndpoint_GetFileImportLogs_WithMerchantFilter_ReturnsData()
{
// create a user
var userId = await this.helper.AddEstateUser("Test Estate", "Api User", "apiuser@example.com");

// create a merchant and use it for the file
var merchant = await this.helper.AddMerchant("Test Estate", "List Filter Merchant", 10, DateTime.MinValue, DateTime.MinValue, default, default);

// create file import log, file and a line associated to the merchant
var filId = await this.helper.AddFileImportLog(this.TestId, DateTime.Now);
var fileId = await this.helper.AddFile(filId, merchant, userId, "test/location/file-filter-list.csv");
await this.helper.AddFileLine(fileId, 1, "filterlistline", "OK");

DateTime start = DateTime.Today.AddDays(-1);
DateTime end = DateTime.Today.AddDays(1);

Result<List<FileImportLog>> result = await this.CreateAndSendHttpRequestMessage<List<FileImportLog>>($"{this.BaseRoute}?merchantId={merchant}&startDate={start:yyyy-MM-dd}&endDate={end:yyyy-MM-dd}", CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
var list = result.Data;
list.ShouldNotBeNull();
list.Count.ShouldBeGreaterThan(0);

var match = list.SelectMany(x => x.FileDetailsList).SelectMany(fd => fd.FileLines).SingleOrDefault(fl => fl.LineContents == "filterlistline");
match.ShouldNotBeNull();
match.LineStatus.ShouldBe("OK");
}

[Fact]
public async Task FileImportEndpoint_GetFileImportLog_WithMerchantFilter_ReturnsData()
{
// create a user
var userId = await this.helper.AddEstateUser("Test Estate", "Api User", "apiuser@example.com");

// create a merchant and use it for the file
var merchant = await this.helper.AddMerchant("Test Estate", "Filter Merchant", 10, DateTime.MinValue, DateTime.MinValue, default, default);

// create file import log, file and a line associated to the merchant
var filId = await this.helper.AddFileImportLog(this.TestId, DateTime.Now);
var fileId = await this.helper.AddFile(filId, merchant, userId, "test/location/file-filter.csv");
await this.helper.AddFileLine(fileId, 1, "filterline", "OK");

Result<DataTransferObjects.FileImportLog> result = await this.CreateAndSendHttpRequestMessage<DataTransferObjects.FileImportLog>($"{this.BaseRoute}/{filId}?merchantId={merchant}", CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
var item = result.Data;
item.ShouldNotBeNull();
item.FileImportLogId.ShouldBe(filId);

var match = item.FileDetailsList.SelectMany(fd => fd.FileLines).SingleOrDefault(fl => fl.LineContents == "filterline");
match.ShouldNotBeNull();
match.LineStatus.ShouldBe("OK");
}

[Fact]
public async Task FileImportEndpoint_GetFileImportLog_ReturnsInsertedData()
{
// create a user
var userId = await this.helper.AddEstateUser("Test Estate", "Api User", "apiuser@example.com");

// pick a merchant
var merchant = await this.context.Merchants.FirstAsync();

// create file import log, file and a line
var filId = await this.helper.AddFileImportLog(this.TestId, DateTime.Now);
var fileId = await this.helper.AddFile(filId, merchant.MerchantId, userId, "test/location/file1.csv");
await this.helper.AddFileLine(fileId, 1, "line1data", "OK");

Result<DataTransferObjects.FileImportLog> result = await this.CreateAndSendHttpRequestMessage<DataTransferObjects.FileImportLog>($"{this.BaseRoute}/{filId}", CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
var item = result.Data;
item.ShouldNotBeNull();
item.FileImportLogId.ShouldBe(filId);

var match = item.FileDetailsList.SelectMany(fd => fd.FileLines).SingleOrDefault(fl => fl.LineContents == "line1data");
match.ShouldNotBeNull();
match.LineStatus.ShouldBe("OK");
}

[Fact]
public async Task FileImportEndpoint_GetFileImportLog_WithMerchantFilter_ReturnsNotFound()
{
// create a user
var userId = await this.helper.AddEstateUser("Test Estate", "Api User", "apiuser@example.com");

// pick a merchant and create another merchant to use as a mismatched filter
var merchant1 = await this.context.Merchants.FirstAsync();
var merchant2 = await this.helper.AddMerchant("Test Estate", "Other Merchant", 50, DateTime.MinValue, DateTime.MinValue, default, default);

// create file import log, file and a line associated to merchant1
var filId = await this.helper.AddFileImportLog(this.TestId, DateTime.Now);
var fileId = await this.helper.AddFile(filId, merchant1.MerchantId, userId, "test/location/file1.csv");
await this.helper.AddFileLine(fileId, 1, "line1data", "OK");

var url = $"{this.BaseRoute}/{filId}?merchantId={merchant2}";
var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
requestMessage.Headers.Add("estateId", this.TestId.ToString());
requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Test");

var response = await this.Client.SendAsync(requestMessage, CancellationToken.None);
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
}

[Fact]
public async Task FileImportEndpoint_GetFileImportLogs_ReturnsInsertedData()
{
Expand Down
4 changes: 3 additions & 1 deletion EstateReportingAPI/Endpoints/FileImportLogEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public static void MapFileImportLogEndpoints(this IEndpointRouteBuilder app)
group = group.RequireAuthorization();
}

group.MapGet("", FileImportHandler.GetFileImportLogList).WithStandardProduces<List<FileImportLog>>();
group.MapGet("/", FileImportHandler.GetFileImportLogList).WithStandardProduces<List<FileImportLog>>();

group.MapGet("/{fileimportlogid}", FileImportHandler.GetFileImportLog).WithStandardProduces<FileImportLog>();
}
}
38 changes: 38 additions & 0 deletions EstateReportingAPI/Handlers/FileImportHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,42 @@ public static async Task<IResult> GetFileImportLogList([FromHeader] Guid estateI
}).ToList()
}).ToList());
}

public static async Task<IResult> GetFileImportLog([FromHeader] Guid estateId,
[FromRoute] Guid fileImportLogId,
[FromQuery] Guid? merchantId,
IMediator mediator,
CancellationToken cancellationToken)
{
FileImportLogQueries.GetFileImportLogQuery query = new(estateId, merchantId, fileImportLogId);
Result<FileImportLog> result = await mediator.Send(query, cancellationToken);

if (result.IsSuccess && result.Data == null)
{
return Results.NotFound();
}

return ResponseFactory.FromResult(result, r => new DataTransferObjects.FileImportLog
{
FileImportLogId = r.FileImportLogId,
ImportLogDateTime = r.ImportLogDateTime,
FileDetailsList = r.FileDetailsList.Select(fd => new DataTransferObjects.FileDetails
{
DateTimeUploaded = fd.DateTimeUploaded,
FileId = fd.FileId,
FileName = fd.FileName,
FileProfile = fd.FileProfile,
MerchantId = fd.MerchantId,
MerchantName = fd.MerchantName,
UploadedBy = fd.UploadedBy,
UserId = fd.UserId,
FileLines = fd.FileLines.Select(fl => new DataTransferObjects.FileLine
{
LineContents = fl.LineContents,
LineNumber = fl.LineNumber,
LineStatus = fl.LineStatus
}).ToList()
}).ToList()
});
}
}
Loading