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 ;
47using Shared . Results ;
58using SimpleResults ;
69using TransactionProcessor . DataTransferObjects . Responses . Contract ;
710using TransactionProcessor . DataTransferObjects . Responses . Operator ;
8-
9- namespace FileProcessor . BusinessLogic . Services ;
10-
1111using System ;
1212using System . Collections . Generic ;
1313using System . IO ;
@@ -17,15 +17,12 @@ namespace FileProcessor.BusinessLogic.Services;
1717using System . Text ;
1818using System . Threading ;
1919using System . Threading . Tasks ;
20- using Azure . Core ;
2120using Common ;
2221using FileAggregate ;
2322using FileFormatHandlers ;
2423using FileImportLogAggregate ;
25- using FileProcessor . DataTransferObjects . Responses ;
26- using FileProcessor . Models ;
24+ using Models ;
2725using Managers ;
28- using MediatR ;
2926using Newtonsoft . Json ;
3027using Requests ;
3128using SecurityService . Client ;
@@ -37,8 +34,19 @@ namespace FileProcessor.BusinessLogic.Services;
3734using Shared . Logger ;
3835using TransactionProcessor . Client ;
3936using 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
4351public 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}
0 commit comments