Skip to content

Commit a6246ba

Browse files
use results on the File Aggregate
1 parent 267ea15 commit a6246ba

5 files changed

Lines changed: 175 additions & 204 deletions

File tree

FileProcessor.BusinessLogic.Tests/DomainServiceHelperTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using FileProcessor.BusinessLogic.Services;
33
using Microsoft.Extensions.Logging.Abstractions;
44
using Microsoft.Identity.Client;
5+
using Shared.EventStore.Helpers;
56
using Shouldly;
67
using SimpleResults;
78
using Xunit;

FileProcessor.BusinessLogic/Services/FileProcessorDomainService.cs

Lines changed: 72 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
using System.Diagnostics.CodeAnalysis;
2-
using System.Reflection.Metadata.Ecma335;
3-
using FileProcessor.Models;
1+
using Shared.EventStore.Helpers;
2+
using TransactionProcessor.DataTransferObjects.Responses.Merchant;
3+
4+
namespace FileProcessor.BusinessLogic.Services;
5+
6+
using System.Diagnostics.CodeAnalysis;
47
using Shared.Results;
58
using SimpleResults;
69
using TransactionProcessor.DataTransferObjects.Responses.Contract;
710
using TransactionProcessor.DataTransferObjects.Responses.Operator;
8-
9-
namespace FileProcessor.BusinessLogic.Services;
10-
1111
using System;
1212
using System.Collections.Generic;
1313
using System.IO;
@@ -17,15 +17,12 @@ namespace FileProcessor.BusinessLogic.Services;
1717
using System.Text;
1818
using System.Threading;
1919
using System.Threading.Tasks;
20-
using Azure.Core;
2120
using Common;
2221
using FileAggregate;
2322
using FileFormatHandlers;
2423
using FileImportLogAggregate;
25-
using FileProcessor.DataTransferObjects.Responses;
26-
using FileProcessor.Models;
24+
using Models;
2725
using Managers;
28-
using MediatR;
2926
using Newtonsoft.Json;
3027
using Requests;
3128
using SecurityService.Client;
@@ -37,8 +34,19 @@ namespace FileProcessor.BusinessLogic.Services;
3734
using Shared.Logger;
3835
using TransactionProcessor.Client;
3936
using TransactionProcessor.DataTransferObjects;
40-
using FileDetails = FileProcessor.Models.FileDetails;
41-
using FileLine = FileProcessor.Models.FileLine;
37+
using FileDetails = Models.FileDetails;
38+
using FileLine = Models.FileLine;
39+
40+
public interface IFileProcessorDomainService
41+
{
42+
Task<Result<Guid>> UploadFile(FileCommands.UploadFileCommand command, CancellationToken cancellationToken);
43+
44+
Task<Result> ProcessUploadedFile(FileCommands.ProcessUploadedFileCommand command,
45+
CancellationToken cancellationToken);
46+
47+
Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTransactionForFileLineCommand command,
48+
CancellationToken cancellationToken);
49+
}
4250

4351
public class FileProcessorDomainService : IFileProcessorDomainService
4452
{
@@ -153,37 +161,33 @@ public async Task<Result<Guid>> UploadFile(FileCommands.UploadFileCommand comman
153161

154162
// Move the file
155163
Result<FileProfile> getFileProfileResult = await this.FileProcessorManager.GetFileProfile(command.FileProfileId, cancellationToken);
156-
if (getFileProfileResult.IsFailed)
157-
{
164+
if (getFileProfileResult.IsFailed) {
158165
return ResultHelpers.CreateFailure(getFileProfileResult);
159166
}
167+
160168
FileProfile fileProfile = getFileProfileResult.Data;
161-
if (fileProfile == null)
162-
{
169+
if (fileProfile == null) {
163170
return Result.NotFound($"No file profile found with Id {command.FileProfileId}");
164171
}
165172

166173
// Copy file from the temp location to file processing listening directory
167174
IFileInfo file = this.FileSystem.FileInfo.New(command.FilePath);
168-
if (file.Exists == false)
169-
{
175+
if (file.Exists == false) {
170176
return Result.NotFound($"File {file.FullName} not found");
171177
}
178+
172179
String originalName = file.Name;
173180

174-
if (this.FileSystem.Directory.Exists(fileProfile.ListeningDirectory) == false)
175-
{
181+
if (this.FileSystem.Directory.Exists(fileProfile.ListeningDirectory) == false) {
176182
return Result.NotFound($"Directory {fileProfile.ListeningDirectory} not found");
177183
}
178184

179185
// Read the file data
180186
String fileContent = null;
181187
//Open file for Read\Write
182-
using (Stream fs = file.Open(FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read))
183-
{
188+
await using (Stream fs = file.Open(FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read)) {
184189
//Create object of StreamReader by passing FileStream object on which it needs to operates on
185-
using (StreamReader sr = new StreamReader(fs))
186-
{
190+
using (StreamReader sr = new StreamReader(fs)) {
187191
//Use ReadToEnd method to read all the content from file
188192
fileContent = await sr.ReadToEndAsync(cancellationToken);
189193
}
@@ -195,7 +199,7 @@ public async Task<Result<Guid>> UploadFile(FileCommands.UploadFileCommand comman
195199
file.MoveTo(fileDestination, overwrite: true);
196200

197201
// Update Import log aggregate
198-
var stateResult= fileImportLogAggregate.AddImportedFile(fileId, command.MerchantId, command.UserId, command.FileProfileId, originalName, fileDestination, command.FileUploadedDateTime);
202+
Result stateResult= fileImportLogAggregate.AddImportedFile(fileId, command.MerchantId, command.UserId, command.FileProfileId, originalName, fileDestination, command.FileUploadedDateTime);
199203
if (stateResult.IsFailed)
200204
return stateResult;
201205

@@ -231,7 +235,10 @@ public async Task<Result> ProcessUploadedFile(FileCommands.ProcessUploadedFileCo
231235
return ResultHelpers.CreateFailure(operatorIdResult);
232236

233237
Logger.LogWarning("About to Create File");
234-
fileAggregate.CreateFile(command.FileImportLogId, command.EstateId, command.MerchantId, command.UserId, command.FileProfileId, command.FilePath, command.FileUploadedDateTime, operatorIdResult.Data);
238+
Result stateResult = fileAggregate.CreateFile(command.FileImportLogId, command.EstateId, command.MerchantId, command.UserId, command.FileProfileId, command.FilePath, command.FileUploadedDateTime, operatorIdResult.Data);
239+
if (stateResult.IsFailed)
240+
return stateResult;
241+
235242
Logger.LogWarning("About to return success");
236243
return Result.Success();
237244

@@ -253,9 +260,9 @@ private async Task<Result<Guid>> GetOperatorIdForFileProfile(Guid estateId, Guid
253260
Logger.LogInformation($"file profile {fileProfileId} not found");
254261
return ResultHelpers.CreateFailure(fileProfileResult);
255262
}
256-
var fileProfile = fileProfileResult.Data;
263+
FileProfile fileProfile = fileProfileResult.Data;
257264

258-
var getTokenResult = await this.GetToken(cancellationToken);
265+
Result<TokenResponse> getTokenResult = await this.GetToken(cancellationToken);
259266
if (getTokenResult.IsFailed) {
260267
return ResultHelpers.CreateFailure(getTokenResult);
261268
}
@@ -279,20 +286,17 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
279286
Result result = await ApplyFileUpdates(async (FileAggregate fileAggregate) => {
280287
FileDetails fileDetails = fileAggregate.GetFile();
281288

282-
if (fileDetails.FileLines.Any() == false)
283-
{
289+
if (fileDetails.FileLines.Any() == false) {
284290
return Result.Invalid($"File Id [{command.FileId}] has no lines added");
285291
}
286292

287293
FileLine fileLine = fileDetails.FileLines.SingleOrDefault(f => f.LineNumber == command.LineNumber);
288294

289-
if (fileLine == null)
290-
{
295+
if (fileLine == null) {
291296
return Result.NotFound($"File Line Number {command.LineNumber} not found in File Id {command.FileId}");
292297
}
293298

294-
if (fileLine.ProcessingResult != ProcessingResult.NotProcessed)
295-
{
299+
if (fileLine.ProcessingResult != ProcessingResult.NotProcessed) {
296300
// Line already processed
297301
return Result.Success();
298302
}
@@ -308,17 +312,20 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
308312
if (this.FileLineCanBeIgnored(fileLine.LineData, fileProfile.FileFormatHandler))
309313
{
310314
// Write something to aggregate to say line was explicity ignored
311-
fileAggregate.RecordFileLineAsIgnored(fileLine.LineNumber);
315+
Result stateResult = fileAggregate.RecordFileLineAsIgnored(fileLine.LineNumber);
316+
if (stateResult.IsFailed)
317+
return stateResult;
312318
return Result.Success();
313319
}
314320

315321
// need to now parse the line (based on the file format), this builds the metadata
316322
Dictionary<String, String> transactionMetadata = this.ParseFileLine(fileLine.LineData, fileProfile.FileFormatHandler);
317323

318-
if (transactionMetadata.Any() == false)
319-
{
324+
if (transactionMetadata.Any() == false) {
320325
// Line failed to parse so record this
321-
fileAggregate.RecordFileLineAsRejected(fileLine.LineNumber, "Invalid Format");
326+
Result stateResult = fileAggregate.RecordFileLineAsRejected(fileLine.LineNumber, "Invalid Format");
327+
if (stateResult.IsFailed)
328+
return stateResult;
322329
return Result.Success();
323330
}
324331

@@ -327,63 +334,55 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
327334
transactionMetadata.Add("FileLineNumber", fileLine.LineNumber.ToString());
328335

329336
String operatorName = fileProfile.OperatorName;
330-
if (transactionMetadata.ContainsKey("OperatorName"))
331-
{
337+
if (transactionMetadata.ContainsKey("OperatorName")) {
332338
// extract the value
333339
operatorName = transactionMetadata["OperatorName"];
334340
transactionMetadata = transactionMetadata.Where(x => x.Key != "OperatorName").ToDictionary(x => x.Key, x => x.Value);
335341
}
336342

337-
var getTokenResult = await this.GetToken(cancellationToken);
338-
if (getTokenResult.IsFailed)
339-
{
343+
Result<TokenResponse> getTokenResult = await this.GetToken(cancellationToken);
344+
if (getTokenResult.IsFailed) {
340345
return ResultHelpers.CreateFailure(getTokenResult);
341346
}
347+
342348
this.TokenResponse = getTokenResult.Data;
343349

344-
Interlocked.Increment(ref FileProcessorDomainService.TransactionNumber);
350+
Interlocked.Increment(ref TransactionNumber);
345351

346352
// Get the merchant details
347-
var getMerchantResult = await this.TransactionProcessorClient.GetMerchant(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken);
348-
if (getMerchantResult.IsFailed)
349-
{
353+
Result<MerchantResponse> getMerchantResult = await this.TransactionProcessorClient.GetMerchant(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken);
354+
if (getMerchantResult.IsFailed) {
350355
return ResultHelpers.CreateFailure(getMerchantResult);
351356
}
352357

353-
var merchant = getMerchantResult.Data;
358+
MerchantResponse merchant = getMerchantResult.Data;
354359

355-
var getContractsResult = await this.TransactionProcessorClient.GetMerchantContracts(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken);
356-
if (getContractsResult.IsFailed)
357-
{
360+
Result<List<ContractResponse>> getContractsResult = await this.TransactionProcessorClient.GetMerchantContracts(this.TokenResponse.AccessToken, fileDetails.EstateId, fileDetails.MerchantId, cancellationToken);
361+
if (getContractsResult.IsFailed) {
358362
return ResultHelpers.CreateFailure(getContractsResult);
359363
}
360364

361365
List<ContractResponse> contracts = getContractsResult.Data;
362366

363-
if (contracts.Any() == false)
364-
{
367+
if (contracts.Any() == false) {
365368
return Result.NotFound($"No contracts found for Merchant Id {fileDetails.MerchantId} on estate Id {fileDetails.EstateId}");
366369
}
367370

368371
ContractResponse? contract = null;
369-
if (fileProfile.OperatorName == "Voucher")
370-
{
372+
if (fileProfile.OperatorName == "Voucher") {
371373
contract = contracts.SingleOrDefault(c => c.Description.Contains(operatorName));
372374
}
373-
else
374-
{
375+
else {
375376
contract = contracts.SingleOrDefault(c => c.OperatorName == operatorName);
376377
}
377378

378-
if (contract == null)
379-
{
379+
if (contract == null) {
380380
return Result.NotFound($"No merchant contract for operator Id {operatorName} found for Merchant Id {merchant.MerchantId}");
381381
}
382382

383383
ContractProduct? product = contract.Products.SingleOrDefault(p => p.Value == null); // TODO: Is this enough or should the name be used and stored in file profile??
384384

385-
if (product == null)
386-
{
385+
if (product == null) {
387386
return Result.NotFound($"No variable value product found on the merchant contract for operator Id {fileProfile.OperatorName} and Merchant Id {merchant.MerchantId}");
388387
}
389388

@@ -393,7 +392,7 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
393392
EstateId = fileDetails.EstateId,
394393
MerchantId = fileDetails.MerchantId,
395394
TransactionDateTime = fileDetails.FileReceivedDateTime,
396-
TransactionNumber = FileProcessorDomainService.TransactionNumber.ToString(),
395+
TransactionNumber = TransactionNumber.ToString(),
397396
TransactionType = "Sale",
398397
ContractId = contract.ContractId,
399398
DeviceIdentifier = merchant.Devices.First().Value,
@@ -422,19 +421,22 @@ public async Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTran
422421
Result<SerialisedMessage> result= await this.TransactionProcessorClient.PerformTransaction(this.TokenResponse.AccessToken, serialisedRequestMessage, cancellationToken);
423422
if (result.IsFailed)
424423
return ResultHelpers.CreateFailure(result);
425-
var serialisedResponseMessage = result.Data;
424+
SerialisedMessage serialisedResponseMessage = result.Data;
426425

427426
// Get the sale transaction response
428427
SaleTransactionResponse saleTransactionResponse = JsonConvert.DeserializeObject<SaleTransactionResponse>(serialisedResponseMessage.SerialisedData);
429428

430-
if (saleTransactionResponse.ResponseCode == "0000")
431-
{
429+
if (saleTransactionResponse.ResponseCode == "0000") {
432430
// record response against file line in file aggregate
433-
fileAggregate.RecordFileLineAsSuccessful(command.LineNumber, saleTransactionResponse.TransactionId);
431+
Result stateResult = fileAggregate.RecordFileLineAsSuccessful(command.LineNumber, saleTransactionResponse.TransactionId);
432+
if (stateResult.IsFailed)
433+
return stateResult;
434434
}
435435
else
436436
{
437-
fileAggregate.RecordFileLineAsFailed(command.LineNumber, saleTransactionResponse.TransactionId, saleTransactionResponse.ResponseCode, saleTransactionResponse.ResponseMessage);
437+
Result stateResult = fileAggregate.RecordFileLineAsFailed(command.LineNumber, saleTransactionResponse.TransactionId, saleTransactionResponse.ResponseCode, saleTransactionResponse.ResponseMessage);
438+
if (stateResult.IsFailed)
439+
return stateResult;
438440
}
439441

440442
return Result.Success();
@@ -510,7 +512,9 @@ private async Task<Result> ProcessFile(Guid fileId,
510512
String[] fileLines = fileContent.Split(fileProfile.LineTerminator);
511513

512514
foreach (String fileLine in fileLines) {
513-
fileAggregate.AddFileLine(fileLine.Trim());
515+
Result stateResult = fileAggregate.AddFileLine(fileLine.Trim());
516+
if (stateResult.IsFailed)
517+
return stateResult;
514518
}
515519
}
516520

@@ -611,35 +615,4 @@ private async Task<Result<TokenResponse>> GetToken(CancellationToken cancellatio
611615

612616
return this.TokenResponse;
613617
}
614-
}
615-
616-
public static class DomainServiceHelper
617-
{
618-
public static Result<T> HandleGetAggregateResult<T>(Result<T> result, Guid aggregateId, bool isNotFoundError = true)
619-
where T : Aggregate, new() // Constraint: T is a subclass of Aggregate and has a parameterless constructor
620-
{
621-
Logger.LogWarning($"Result is {JsonConvert.SerializeObject(result)}");
622-
Logger.LogWarning($"aggregateId is {aggregateId}");
623-
Logger.LogWarning($"isNotFoundError is {isNotFoundError}");
624-
625-
if (result.IsFailed && result.Status != ResultStatus.NotFound) {
626-
Logger.LogWarning("In here 1");
627-
return ResultHelpers.CreateFailure(result);
628-
}
629-
630-
if (result.Status == ResultStatus.NotFound && isNotFoundError)
631-
{
632-
Logger.LogWarning("In here 2");
633-
return ResultHelpers.CreateFailure(result);
634-
}
635-
636-
Logger.LogWarning("In here 3");
637-
T aggregate = result.Status switch
638-
{
639-
ResultStatus.NotFound => new T { AggregateId = aggregateId }, // Set AggregateId when creating a new instance
640-
_ => result.Data
641-
};
642-
643-
return Result.Success(aggregate);
644-
}
645618
}

FileProcessor.BusinessLogic/Services/IFileProcessorDomainService.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,5 @@ namespace FileProcessor.BusinessLogic.Services
99
using MediatR;
1010
using Requests;
1111

12-
public interface IFileProcessorDomainService
13-
{
14-
Task<Result<Guid>> UploadFile(FileCommands.UploadFileCommand command, CancellationToken cancellationToken);
15-
16-
Task<Result> ProcessUploadedFile(FileCommands.ProcessUploadedFileCommand command,
17-
CancellationToken cancellationToken);
18-
19-
Task<Result> ProcessTransactionForFileLine(FileCommands.ProcessTransactionForFileLineCommand command,
20-
CancellationToken cancellationToken);
21-
}
12+
2213
}

0 commit comments

Comments
 (0)