From 6987648f224e2c44c532b01bfb59f44a74f16516 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Tue, 11 Feb 2025 10:03:30 +0000 Subject: [PATCH 1/3] wip --- .../MerchantStatementAggregateTests.cs | 221 +++++++++++ .../MerchantStatementAggregate.cs | 311 ++++++++++++++++ .../Models/SettledFee.cs | 10 + .../MerchantStatementRequestHandler.cs | 87 +++++ .../Requests/MerchantStatementCommands.cs | 27 ++ .../MerchantStatementDomainService.cs | 346 ++++++++++++++++++ .../MerchantStatementDomainEvents.cs | 37 ++ .../Merchant/MerchantStatement.cs | 130 +++++++ .../Merchant/MerchantStatementLine.cs | 45 +++ TransactionProcessor.Testing/TestData.cs | 38 ++ 10 files changed, 1252 insertions(+) create mode 100644 TransactionProcessor.Aggregates.Tests/MerchantStatementAggregateTests.cs create mode 100644 TransactionProcessor.Aggregates/MerchantStatementAggregate.cs create mode 100644 TransactionProcessor.Aggregates/Models/SettledFee.cs create mode 100644 TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs create mode 100644 TransactionProcessor.BusinessLogic/Requests/MerchantStatementCommands.cs create mode 100644 TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs create mode 100644 TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs create mode 100644 TransactionProcessor.Models/Merchant/MerchantStatement.cs create mode 100644 TransactionProcessor.Models/Merchant/MerchantStatementLine.cs diff --git a/TransactionProcessor.Aggregates.Tests/MerchantStatementAggregateTests.cs b/TransactionProcessor.Aggregates.Tests/MerchantStatementAggregateTests.cs new file mode 100644 index 00000000..1e3608c9 --- /dev/null +++ b/TransactionProcessor.Aggregates.Tests/MerchantStatementAggregateTests.cs @@ -0,0 +1,221 @@ +using Shouldly; +using TransactionProcessor.Models.Merchant; +using TransactionProcessor.Testing; + +namespace TransactionProcessor.Aggregates.Tests +{ + public class MerchantStatementAggregateTests + { + [Fact] + public void MerchantStatementAggregate_CanBeCreated_IsCreated() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + + merchantStatementAggregate.ShouldNotBeNull(); + merchantStatementAggregate.AggregateId.ShouldBe(TestData.MerchantStatementId); + } + + [Fact] + public void MerchantStatementAggregate_AddTransactionToStatement_TransactionAddedToStatement() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + + MerchantStatement merchantStatement = merchantStatementAggregate.GetStatement(true); + var statementLines = merchantStatement.GetStatementLines(); + statementLines.ShouldNotBeNull(); + statementLines.ShouldNotBeEmpty(); + statementLines.Count.ShouldBe(1); + } + + [Fact] + public void MerchantStatementAggregate_AddTransactionToStatement_DuplicateTransaction_SilentlyHandled() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + + MerchantStatement merchantStatement = merchantStatementAggregate.GetStatement(true); + var statementLines = merchantStatement.GetStatementLines(); + statementLines.ShouldNotBeNull(); + statementLines.ShouldNotBeEmpty(); + statementLines.Count.ShouldBe(1); + } + + [Fact] + public void MerchantStatementAggregate_AddSettledFeeToStatement_FeeAddedToStatement() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.SettledFee1); + + MerchantStatement merchantStatement = merchantStatementAggregate.GetStatement(true); + var statementLines = merchantStatement.GetStatementLines(); + statementLines.ShouldNotBeNull(); + statementLines.ShouldNotBeEmpty(); + statementLines.Count.ShouldBe(1); + } + + [Fact] + public void MerchantStatementAggregate_AddSettledFeeToStatement_DuplicateFee_Silentlyhandled() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.SettledFee1); + + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.SettledFee1); + + MerchantStatement merchantStatement = merchantStatementAggregate.GetStatement(true); + var statementLines = merchantStatement.GetStatementLines(); + statementLines.ShouldNotBeNull(); + statementLines.ShouldNotBeEmpty(); + statementLines.Count.ShouldBe(1); + } + + [Fact] + public void MerchantStatementAggregate_GenerateStatement_StatementIsGenerated() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.SettledFee1); + merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + + var merchantStatement = merchantStatementAggregate.GetStatement(); + merchantStatement.IsGenerated.ShouldBeTrue(); + } + + [Fact] + public void MerchantStatementAggregate_GenerateStatement_StatementNotCreated_ErrorThrown() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + + Should.Throw(() => + { + merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + }); + } + + [Fact] + public void MerchantStatementAggregate_GenerateStatement_StatementAlreadyGenerated_ErrorThrown() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.SettledFee1); + merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + + Should.Throw(() => + { + merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + }); + } + + [Fact] + public void MerchantStatementAggregate_GenerateStatement_StatementHasNoTransactionsOrSettledFees_ErrorThrown() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + + Should.Throw(() => + { + merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + }); + } + + [Fact] + public void MerchantStatementAggregate_EmailStatement_StatementHasBeenEmailed() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, + TestData.SettledFee1); + merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + + merchantStatementAggregate.EmailStatement(TestData.StatementEmailedDate, TestData.MessageId); + + MerchantStatement statement = merchantStatementAggregate.GetStatement(false); + statement.HasBeenEmailed.ShouldBeTrue(); + } + + [Fact] + public void MerchantStatementAggregate_EmailStatement_NotCreated_ErrorThrown() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + //merchantStatementAggregate.CreateStatement(TestData.EstateId, TestData.MerchantId, TestData.StatementCreateDate); + //merchantStatementAggregate.AddTransactionToStatement(TestData.Transaction1); + //merchantStatementAggregate.AddSettledFeeToStatement(TestData.SettledFee1); + //merchantStatementAggregate.GenerateStatement(TestData.StatementGeneratedDate); + + Should.Throw(() => + { + merchantStatementAggregate.EmailStatement(TestData.StatementEmailedDate,TestData.MessageId); + }); + } + + [Fact] + public void MerchantStatementAggregate_EmailStatement_NotGenerated_ErrorThrown() + { + MerchantStatementAggregate merchantStatementAggregate = MerchantStatementAggregate.Create(TestData.MerchantStatementId); + merchantStatementAggregate.AddTransactionToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.Transaction1); + merchantStatementAggregate.AddSettledFeeToStatement(TestData.MerchantStatementId, + TestData.EventId1, + TestData.StatementCreateDate, + TestData.EstateId, + TestData.MerchantId, TestData.SettledFee1); + + Should.Throw(() => + { + merchantStatementAggregate.EmailStatement(TestData.StatementEmailedDate, TestData.MessageId); + }); + } + } +} diff --git a/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs b/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs new file mode 100644 index 00000000..c2c4a6f2 --- /dev/null +++ b/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs @@ -0,0 +1,311 @@ +using System.Diagnostics.CodeAnalysis; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.Aggregate; +using Shared.General; +using TransactionProcessor.Aggregates.Models; +using TransactionProcessor.DomainEvents; +using TransactionProcessor.Models.Merchant; + +namespace TransactionProcessor.Aggregates +{ + public static class MerchantStatementAggregateExtenions{ + public static void AddSettledFeeToStatement(this MerchantStatementAggregate aggregate, + Guid statementId, + Guid eventId, + DateTime createdDate, + Guid estateId, + Guid merchantId, + SettledFee settledFee){ + // Create statement id required + aggregate.CreateStatement(statementId, createdDate, estateId, merchantId); + + SettledFeeAddedToStatementEvent settledFeeAddedToStatementEvent = + new SettledFeeAddedToStatementEvent(aggregate.AggregateId, + eventId, + aggregate.EstateId, + aggregate.MerchantId, + settledFee.SettledFeeId, + settledFee.TransactionId, + settledFee.DateTime, + settledFee.Amount); + + aggregate.ApplyAndAppend(settledFeeAddedToStatementEvent); + } + + public static void AddTransactionToStatement(this MerchantStatementAggregate aggregate, + Guid statementId, + Guid eventId, + DateTime createdDate, + Guid estateId, + Guid merchantId, + Transaction transaction){ + // Create statement id required + aggregate.CreateStatement(statementId, createdDate, estateId, merchantId); + + TransactionAddedToStatementEvent transactionAddedToStatementEvent = new TransactionAddedToStatementEvent(aggregate.AggregateId, + eventId, + aggregate.EstateId, + aggregate.MerchantId, + transaction.TransactionId, + transaction.DateTime, + transaction.Amount); + + aggregate.ApplyAndAppend(transactionAddedToStatementEvent); + } + + private static void CreateStatement(this MerchantStatementAggregate aggregate, + Guid statementId, + DateTime createdDate, + Guid estateId, + Guid merchantId) + { + if (aggregate.IsCreated == false) + { + Guard.ThrowIfInvalidGuid(statementId, nameof(statementId)); + Guard.ThrowIfInvalidGuid(estateId, nameof(estateId)); + Guard.ThrowIfInvalidGuid(merchantId, nameof(merchantId)); + + StatementCreatedEvent statementCreatedEvent = new StatementCreatedEvent(statementId, estateId, merchantId, createdDate); + + aggregate.ApplyAndAppend(statementCreatedEvent); + } + } + + public static void EmailStatement(this MerchantStatementAggregate aggregate, + DateTime emailedDateTime, + Guid messageId) + { + aggregate.EnsureStatementHasBeenCreated(); + aggregate.EnsureStatementHasBeenGenerated(); + + StatementEmailedEvent statementEmailedEvent = new StatementEmailedEvent(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, emailedDateTime, messageId); + + aggregate.ApplyAndAppend(statementEmailedEvent); + } + + public static void GenerateStatement(this MerchantStatementAggregate aggregate, + DateTime generatedDateTime) + { + aggregate.EnsureStatementHasNotAlreadyBeenGenerated(); + + if (aggregate.Transactions.Any() == false && aggregate.SettledFees.Any() == false) + { + throw new InvalidOperationException("Statement has no transactions or settled fees"); + } + + StatementGeneratedEvent statementGeneratedEvent = new StatementGeneratedEvent(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, generatedDateTime); + + aggregate.ApplyAndAppend(statementGeneratedEvent); + } + + public static MerchantStatement GetStatement(this MerchantStatementAggregate aggregate, Boolean includeStatementLines = false) + { + MerchantStatement merchantStatement = new MerchantStatement + { + EstateId = aggregate.EstateId, + MerchantId = aggregate.MerchantId, + MerchantStatementId = aggregate.AggregateId, + IsCreated = aggregate.IsCreated, + IsGenerated = aggregate.IsGenerated, + HasBeenEmailed = aggregate.HasBeenEmailed, + StatementCreatedDateTime = aggregate.CreatedDateTime, + StatementGeneratedDateTime = aggregate.GeneratedDateTime + }; + + if (includeStatementLines) + { + foreach (Transaction transaction in aggregate.Transactions) + { + merchantStatement.AddStatementLine(new MerchantStatementLine + { + Amount = transaction.Amount, + DateTime = transaction.DateTime, + Description = string.Empty, + LineType = 1 // Transaction + }); + } + + foreach (SettledFee settledFee in aggregate.SettledFees) + { + merchantStatement.AddStatementLine(new MerchantStatementLine + { + Amount = settledFee.Amount, + DateTime = settledFee.DateTime, + Description = string.Empty, + LineType = 2 // Settled Fee + }); + } + } + + return merchantStatement; + } + + private static void EnsureStatementHasBeenCreated(this MerchantStatementAggregate aggregate) + { + if (aggregate.IsCreated == false) + { + throw new InvalidOperationException("Statement has not been created"); + } + } + + private static void EnsureStatementHasBeenGenerated(this MerchantStatementAggregate aggregate) + { + if (aggregate.IsGenerated == false) + { + throw new InvalidOperationException("Statement has not been generated"); + } + } + + private static void EnsureStatementHasNotAlreadyBeenGenerated(this MerchantStatementAggregate aggregate) + { + if (aggregate.IsGenerated) + { + throw new InvalidOperationException("Statement has already been generated"); + } + } + + public static void PlayEvent(this MerchantStatementAggregate aggregate, StatementCreatedEvent domainEvent) + { + aggregate.IsCreated = true; + aggregate.EstateId = domainEvent.EstateId; + aggregate.MerchantId = domainEvent.MerchantId; + aggregate.CreatedDateTime = domainEvent.DateCreated; + } + + public static void PlayEvent(this MerchantStatementAggregate aggregate, TransactionAddedToStatementEvent domainEvent) + { + aggregate.EstateId = domainEvent.EstateId; + aggregate.MerchantId = domainEvent.MerchantId; + + aggregate.Transactions.Add(new Transaction(domainEvent.TransactionId, domainEvent.TransactionDateTime, domainEvent.TransactionValue)); + } + + public static void PlayEvent(this MerchantStatementAggregate aggregate, SettledFeeAddedToStatementEvent domainEvent) + { + aggregate.EstateId = domainEvent.EstateId; + aggregate.MerchantId = domainEvent.MerchantId; + + aggregate.SettledFees.Add(new SettledFee(domainEvent.SettledFeeId, domainEvent.TransactionId, domainEvent.SettledDateTime, domainEvent.SettledValue)); + } + + public static void PlayEvent(this MerchantStatementAggregate aggregate, StatementGeneratedEvent domainEvent) + { + aggregate.IsGenerated = true; + aggregate.GeneratedDateTime = domainEvent.DateGenerated; + } + + public static void PlayEvent(this MerchantStatementAggregate aggregate, StatementEmailedEvent domainEvent) + { + aggregate.HasBeenEmailed = true; + aggregate.EmailedDateTime = domainEvent.DateEmailed; + aggregate.EmailMessageId = domainEvent.MessageId; + } + } + + public record MerchantStatementAggregate : Aggregate + { + #region Fields + + /// + /// The created date time + /// + internal DateTime CreatedDateTime; + + /// + /// The emailed date time + /// + internal DateTime EmailedDateTime; + + /// + /// The email message identifier + /// + internal Guid EmailMessageId; + + /// + /// The estate identifier + /// + internal Guid EstateId; + + /// + /// The generated date time + /// + internal DateTime GeneratedDateTime; + + /// + /// The has been emailed + /// + internal Boolean HasBeenEmailed; + + /// + /// The is created + /// + internal Boolean IsCreated; + + /// + /// The is generated + /// + internal Boolean IsGenerated; + + /// + /// The merchant identifier + /// + internal Guid MerchantId; + + /// + /// The settled fees + /// + internal readonly List SettledFees; + + /// + /// The transactions + /// + internal readonly List Transactions; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + [ExcludeFromCodeCoverage] + public MerchantStatementAggregate() + { + // Nothing here + this.Transactions = new List(); + this.SettledFees = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The aggregate identifier. + private MerchantStatementAggregate(Guid aggregateId) + { + Guard.ThrowIfInvalidGuid(aggregateId, "Aggregate Id cannot be an Empty Guid"); + + this.AggregateId = aggregateId; + this.Transactions = new List(); + this.SettledFees = new List(); + } + + #endregion + + #region Methods + + public static MerchantStatementAggregate Create(Guid aggregateId) + { + return new MerchantStatementAggregate(aggregateId); + } + + public override void PlayEvent(IDomainEvent domainEvent) => MerchantStatementAggregateExtenions.PlayEvent(this, (dynamic)domainEvent); + + [ExcludeFromCodeCoverage] + protected override Object GetMetadata() + { + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.Aggregates/Models/SettledFee.cs b/TransactionProcessor.Aggregates/Models/SettledFee.cs new file mode 100644 index 00000000..12833133 --- /dev/null +++ b/TransactionProcessor.Aggregates/Models/SettledFee.cs @@ -0,0 +1,10 @@ +using System.Diagnostics.CodeAnalysis; + +namespace TransactionProcessor.Aggregates.Models +{ + [ExcludeFromCodeCoverage] + public record SettledFee(Guid SettledFeeId, Guid TransactionId, DateTime DateTime, Decimal Amount); + + [ExcludeFromCodeCoverage] + public record Transaction(Guid TransactionId, DateTime DateTime, Decimal Amount); +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs b/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs new file mode 100644 index 00000000..76741269 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs @@ -0,0 +1,87 @@ +using TransactionProcessor.BusinessLogic.Requests; + +namespace EstateManagement.BusinessLogic.RequestHandlers +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using MediatR; + using Services; + using SimpleResults; + using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; + + /// + /// + /// + /// + /// + /// + public class MerchantStatementRequestHandler : IRequestHandler, + IRequestHandler, + IRequestHandler, + IRequestHandler + { + #region Fields + + /// + /// The domain service + /// + private readonly IMerchantStatementDomainService MerchantStatementDomainService; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The merchant statement domain service. + public MerchantStatementRequestHandler(IMerchantStatementDomainService merchantStatementDomainService) + { + this.MerchantStatementDomainService = merchantStatementDomainService; + } + + #endregion + + #region Methods + + /// + /// Handles the specified request. + /// + /// The request. + /// The cancellation token. + /// + public async Task Handle(MerchantStatementCommands.AddTransactionToMerchantStatementCommand command, + CancellationToken cancellationToken) + { + return await this.MerchantStatementDomainService.AddTransactionToStatement(command, + cancellationToken); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The cancellation token. + /// + public async Task Handle(MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command, + CancellationToken cancellationToken) + { + return await this.MerchantStatementDomainService.AddSettledFeeToStatement(command, + cancellationToken); + } + + #endregion + + public async Task Handle(MerchantCommands.GenerateMerchantStatementCommand command, CancellationToken cancellationToken) + { + return await this.MerchantStatementDomainService.GenerateStatement(command, cancellationToken); + } + + public async Task Handle(MerchantStatementCommands.EmailMerchantStatementCommand command, + CancellationToken cancellationToken) + { + return await this.MerchantStatementDomainService.EmailStatement(command, cancellationToken); + } + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Requests/MerchantStatementCommands.cs b/TransactionProcessor.BusinessLogic/Requests/MerchantStatementCommands.cs new file mode 100644 index 00000000..f3f7d421 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Requests/MerchantStatementCommands.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using MediatR; +using SimpleResults; + +namespace TransactionProcessor.BusinessLogic.Requests; + +[ExcludeFromCodeCoverage] +public record MerchantStatementCommands { + public record AddTransactionToMerchantStatementCommand(Guid EstateId, + Guid MerchantId, + DateTime TransactionDateTime, + Decimal? TransactionAmount, + Boolean IsAuthorised, + Guid TransactionId) : IRequest; + + public record EmailMerchantStatementCommand(Guid EstateId, + Guid MerchantId, + Guid MerchantStatementId) : IRequest; + + public record AddSettledFeeToMerchantStatementCommand(Guid EstateId, + Guid MerchantId, + DateTime SettledDateTime, + Decimal SettledAmount, + Guid TransactionId, + Guid SettledFeeId) : IRequest; +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs b/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs new file mode 100644 index 00000000..2a4aa256 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs @@ -0,0 +1,346 @@ +using MessagingService.Client; +using MessagingService.DataTransferObjects; +using SecurityService.Client; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.Aggregate; +using Shared.Exceptions; +using Shared.General; +using Shared.Results; +using SimpleResults; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using System.IO.Abstractions; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TransactionProcessor.Aggregates.Models; +using TransactionProcessor.Aggregates; +using TransactionProcessor.BusinessLogic.Requests; +using TransactionProcessor.Database.Entities; + +namespace TransactionProcessor.BusinessLogic.Services +{ + public interface IMerchantStatementDomainService + { + #region Methods + + Task AddTransactionToStatement(MerchantStatementCommands.AddTransactionToMerchantStatementCommand command, CancellationToken cancellationToken); + + Task AddSettledFeeToStatement(MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command, CancellationToken cancellationToken); + + Task GenerateStatement(MerchantCommands.GenerateMerchantStatementCommand command, CancellationToken cancellationToken); + + Task EmailStatement(MerchantStatementCommands.EmailMerchantStatementCommand command, CancellationToken cancellationToken); + + #endregion + } + + public class MerchantStatementDomainService : IMerchantStatementDomainService + { + #region Fields + + /// + /// The merchant aggregate repository + /// + private readonly IAggregateRepository MerchantAggregateRepository; + + /// + /// The merchant statement aggregate repository + /// + private readonly IAggregateRepository MerchantStatementAggregateRepository; + + private readonly IAggregateRepository EstateAggregateRepository; + + private readonly ITransactionRe EstateManagementRepository; + + private readonly IStatementBuilder StatementBuilder; + + private readonly IMessagingServiceClient MessagingServiceClient; + + private readonly ISecurityServiceClient SecurityServiceClient; + + private readonly IFileSystem FileSystem; + + private readonly IPDFGenerator PdfGenerator; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The merchant aggregate repository. + /// The merchant statement aggregate repository. + /// The estate management repository. + /// The statement builder. + /// The messaging service client. + /// The security service client. + /// The file system. + /// The PDF generator. + public MerchantStatementDomainService(IAggregateRepository merchantAggregateRepository, + IAggregateRepository merchantStatementAggregateRepository, + IEstateManagementRepository estateManagementRepository, + IStatementBuilder statementBuilder, + IMessagingServiceClient messagingServiceClient, + ISecurityServiceClient securityServiceClient, + IFileSystem fileSystem, + IPDFGenerator pdfGenerator) + { + this.MerchantAggregateRepository = merchantAggregateRepository; + this.MerchantStatementAggregateRepository = merchantStatementAggregateRepository; + this.EstateManagementRepository = estateManagementRepository; + this.StatementBuilder = statementBuilder; + this.MessagingServiceClient = messagingServiceClient; + this.SecurityServiceClient = securityServiceClient; + this.FileSystem = fileSystem; + this.PdfGenerator = pdfGenerator; + } + + #endregion + + #region Methods + + private async Task ApplyUpdates(Func> action, Guid estateId, Guid statementId, CancellationToken cancellationToken, Boolean isNotFoundError = true) + { + try + { + Result getMerchantStatementResult = await this.GetLatestVersion(statementId, cancellationToken); + + Result merchantStatementAggregateResult = + DomainServiceHelper.HandleGetAggregateResult(getMerchantStatementResult, statementId, isNotFoundError); + if (merchantStatementAggregateResult.IsFailed) + return ResultHelpers.CreateFailure(merchantStatementAggregateResult); + + MerchantStatementAggregate merchantStatementAggregate = merchantStatementAggregateResult.Data; + + Result result = await action(merchantStatementAggregate); + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + Result saveResult = await this.MerchantStatementAggregateRepository.SaveChanges(merchantStatementAggregate, cancellationToken); + if (saveResult.IsFailed) + return ResultHelpers.CreateFailure(saveResult); + + return Result.Success(); + } + catch (Exception ex) + { + return Result.Failure(ex.GetExceptionMessages()); + } + } + + [ExcludeFromCodeCoverage(Justification = "This will get replaced by metrics soon")] + private async Task> GetLatestVersion(Guid statementId, CancellationToken cancellationToken) + { + Stopwatch sw = Stopwatch.StartNew(); + + Result merchantStatementAggregate = + await this.MerchantStatementAggregateRepository.GetLatestVersion(statementId, cancellationToken); + + sw.Stop(); + Int64 elapsedTime = sw.ElapsedMilliseconds; + + if (elapsedTime > 1000) + { + Logger.LogWarning($"Rehydration of MerchantStatementAggregate Id [{statementId}] took {elapsedTime} ms"); + } + + return merchantStatementAggregate; + } + + public async Task AddSettledFeeToStatement(MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command, + CancellationToken cancellationToken) + { + // Work out the next statement date + DateTime nextStatementDate = CalculateStatementDate(command.SettledDateTime); + + Guid statementId = GuidCalculator.Combine(command.MerchantId, nextStatementDate.ToGuid()); + Guid settlementFeeId = GuidCalculator.Combine(command.TransactionId, command.SettledFeeId); + + Result result = await ApplyUpdates( + async (MerchantStatementAggregate merchantStatementAggregate) => { + + SettledFee settledFee = new SettledFee + { + DateTime = command.SettledDateTime, + Amount = command.SettledAmount, + TransactionId = command.TransactionId, + SettledFeeId = settlementFeeId + }; + + Guid eventId = IdGenerationService.GenerateEventId(new + { + command.TransactionId, + settlementFeeId, + settledFee, + command.SettledDateTime, + }); + + merchantStatementAggregate.AddSettledFeeToStatement(statementId, eventId, nextStatementDate, command.EstateId, command.MerchantId, settledFee); + + return Result.Success(); + }, + command.EstateId, statementId, cancellationToken, false); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return Result.Success(); + } + + /// + /// + /// + /// + /// + internal static DateTime CalculateStatementDate(DateTime eventDateTime) + { + var calculatedDateTime = eventDateTime.Date.AddMonths(1); + + return new DateTime(calculatedDateTime.Year, calculatedDateTime.Month, 1); + } + + /// + /// Generates the statement. + /// + /// The estate identifier. + /// The merchant identifier. + /// The statement date. + /// The cancellation token. + /// + public async Task GenerateStatement(MerchantCommands.GenerateMerchantStatementCommand command, CancellationToken cancellationToken) + { + Guid statementId = GuidCalculator.Combine(command.MerchantId, command.RequestDto.MerchantStatementDate.ToGuid()); + + Result result = await ApplyUpdates( + async (MerchantStatementAggregate merchantStatementAggregate) => { + + merchantStatementAggregate.GenerateStatement(DateTime.Now); + + return Result.Success(); + }, + command.EstateId, statementId, cancellationToken, false); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return Result.Success(); + } + + public async Task EmailStatement(MerchantStatementCommands.EmailMerchantStatementCommand command, + CancellationToken cancellationToken) + { + Result result = await ApplyUpdates( + async (MerchantStatementAggregate merchantStatementAggregate) => { + + StatementHeader statementHeader = await this.EstateManagementRepository.GetStatement(command.EstateId, command.MerchantStatementId, cancellationToken); + + String html = await this.StatementBuilder.GetStatementHtml(statementHeader, cancellationToken); + + String base64 = await this.PdfGenerator.CreatePDF(html, cancellationToken); + + SendEmailRequest sendEmailRequest = new SendEmailRequest + { + Body = "Please find attached this months statement.", + ConnectionIdentifier = command.EstateId, + FromAddress = "golfhandicapping@btinternet.com", // TODO: lookup from config + IsHtml = true, + Subject = $"Merchant Statement for {statementHeader.StatementDate}", + // MessageId = command.MerchantStatementId, + ToAddresses = new List + { + statementHeader.MerchantEmail + }, + EmailAttachments = new List + { + new EmailAttachment + { + FileData = base64, + FileType = FileType.PDF, + Filename = $"merchantstatement{statementHeader.StatementDate}.pdf" + } + } + }; + + Guid messageId = IdGenerationService.GenerateEventId(new + { + command.MerchantStatementId, + DateTime.Now + }); + + sendEmailRequest.MessageId = messageId; + + this.TokenResponse = await Helpers.GetToken(this.TokenResponse, this.SecurityServiceClient, cancellationToken); + + var sendEmailResponseResult = await this.MessagingServiceClient.SendEmail(this.TokenResponse.AccessToken, sendEmailRequest, cancellationToken); + //if (sendEmailResponseResult.IsFailed) { + // // TODO: record a failed event?? + //} + merchantStatementAggregate.EmailStatement(DateTime.Now, messageId); + + return Result.Success(); + }, + command.EstateId, command.MerchantStatementId, cancellationToken); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return Result.Success(); + } + + /// + /// The token response + /// + private TokenResponse TokenResponse; + + public async Task AddTransactionToStatement(MerchantStatementCommands.AddTransactionToMerchantStatementCommand command, + CancellationToken cancellationToken) + { + // Transaction Completed arrives(if this is a logon transaction or failed then return) + if (command.IsAuthorised == false) + return Result.Success(); + if (command.TransactionAmount.HasValue == false) + return Result.Success(); + + // Work out the next statement date + DateTime nextStatementDate = CalculateStatementDate(command.TransactionDateTime); + + Guid statementId = GuidCalculator.Combine(command.MerchantId, nextStatementDate.ToGuid()); + + Result result = await ApplyUpdates( + async (MerchantStatementAggregate merchantStatementAggregate) => { + + // Add transaction to statement + Models.MerchantStatement.Transaction transaction = new() + { + DateTime = command.TransactionDateTime, + Amount = command.TransactionAmount.GetValueOrDefault(0), + TransactionId = command.TransactionId + }; + + Guid eventId = IdGenerationService.GenerateEventId(new + { + command.TransactionId, + TransactionAmount = command.TransactionAmount.GetValueOrDefault(0), + command.TransactionDateTime, + }); + + merchantStatementAggregate.AddTransactionToStatement(statementId, eventId, nextStatementDate, command.EstateId, command.MerchantId, transaction); + + return Result.Success(); + }, + command.EstateId, statementId, cancellationToken, false); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return Result.Success(); + } + + #endregion + } +} diff --git a/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs b/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs new file mode 100644 index 00000000..743d9364 --- /dev/null +++ b/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs @@ -0,0 +1,37 @@ +using Shared.DomainDrivenDesign.EventSourcing; + +namespace TransactionProcessor.DomainEvents +{ + public record SettledFeeAddedToStatementEvent(Guid MerchantStatementId, + Guid EventId, + Guid EstateId, + Guid MerchantId, + Guid SettledFeeId, + Guid TransactionId, + DateTime SettledDateTime, + Decimal SettledValue) : DomainEvent(MerchantStatementId, EventId); + + public record TransactionAddedToStatementEvent(Guid MerchantStatementId, + Guid EventId, + Guid EstateId, + Guid MerchantId, + Guid TransactionId, + DateTime TransactionDateTime, + Decimal TransactionValue) : DomainEvent(MerchantStatementId, EventId); + + public record StatementGeneratedEvent(Guid MerchantStatementId, + Guid EstateId, + Guid MerchantId, + DateTime DateGenerated) : DomainEvent(MerchantStatementId, Guid.NewGuid()); + + public record StatementEmailedEvent(Guid MerchantStatementId, + Guid EstateId, + Guid MerchantId, + DateTime DateEmailed, + Guid MessageId) : DomainEvent(MerchantStatementId, Guid.NewGuid()); + + public record StatementCreatedEvent(Guid MerchantStatementId, + Guid EstateId, + Guid MerchantId, + DateTime DateCreated) : DomainEvent(MerchantStatementId, Guid.NewGuid()); +} \ No newline at end of file diff --git a/TransactionProcessor.Models/Merchant/MerchantStatement.cs b/TransactionProcessor.Models/Merchant/MerchantStatement.cs new file mode 100644 index 00000000..78fc0dbb --- /dev/null +++ b/TransactionProcessor.Models/Merchant/MerchantStatement.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace TransactionProcessor.Models.Merchant; + +[ExcludeFromCodeCoverage] +public class MerchantStatement +{ + #region Fields + + /// + /// The statement lines + /// + private readonly List StatementLines; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public MerchantStatement() + { + this.StatementLines = new List(); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the estate identifier. + /// + /// + /// The estate identifier. + /// + public Guid EstateId { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is created. + /// + /// + /// true if this instance is created; otherwise, false. + /// + public Boolean IsCreated { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is generated. + /// + /// + /// true if this instance is generated; otherwise, false. + /// + public Boolean IsGenerated { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is emailed. + /// + /// + /// true if this instance is emailed; otherwise, false. + /// + public Boolean HasBeenEmailed { get; set; } + + /// + /// Gets or sets the merchant identifier. + /// + /// + /// The merchant identifier. + /// + public Guid MerchantId { get; set; } + + /// + /// Gets or sets the merchant statement identifier. + /// + /// + /// The merchant statement identifier. + /// + public Guid MerchantStatementId { get; set; } + + /// + /// Gets or sets the statement created date time. + /// + /// + /// The statement created date time. + /// + public DateTime StatementCreatedDateTime { get; set; } + + /// + /// Gets or sets the statement generated date time. + /// + /// + /// The statement generated date time. + /// + public DateTime StatementGeneratedDateTime { get; set; } + + /// + /// Gets or sets the statement number. + /// + /// + /// The statement number. + /// + public Int32 StatementNumber { get; set; } // TODO: How is this allocated?? + + #endregion + + #region Methods + + /// + /// Adds the statement line. + /// + /// The statement line. + public void AddStatementLine(MerchantStatementLine statementLine) + { + this.StatementLines.Add(statementLine); + } + + /// + /// Gets the statement lines. + /// + /// + public List GetStatementLines() + { + //return this.StatementLines.OrderBy(s => s.DateTime).ToList(); + return this.StatementLines.OrderBy(s => s.DateTime).ToList(); + } + + #endregion +} \ No newline at end of file diff --git a/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs b/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs new file mode 100644 index 00000000..00ec7682 --- /dev/null +++ b/TransactionProcessor.Models/Merchant/MerchantStatementLine.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace TransactionProcessor.Models.Merchant; + +[ExcludeFromCodeCoverage] +public class MerchantStatementLine +{ + #region Properties + + /// + /// Gets or sets the amount. + /// + /// + /// The amount. + /// + public Decimal Amount { get; set; } + + /// + /// Gets or sets the date time. + /// + /// + /// The date time. + /// + public DateTime DateTime { get; set; } + + /// + /// Gets or sets the description. + /// + /// + /// The description. + /// + public String Description { get; set; } + + // TODO: maybe make this an enum type + /// + /// Gets or sets the type of the line. + /// + /// + /// The type of the line. + /// + public Int32 LineType { get; set; } + + #endregion +} \ No newline at end of file diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index 0a27665a..6c0a1b44 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -31,10 +31,48 @@ namespace TransactionProcessor.Testing using System.Linq; using ContractProductTransactionFeeModel = Models.Contract.ContractProductTransactionFee; using static TransactionProcessor.DomainEvents.ContractDomainEvents; + using TransactionProcessor.Aggregates.Models; public class TestData { #region Fields + public static Guid TransactionId1 = Guid.Parse("82E1ACE2-EA34-4501-832D-1DB97B8B4294"); + + public static DateTime TransactionDateTime1 = new DateTime(2021, 12, 10, 11, 00, 00); + + public static DateTime TransactionDateTime2 = new DateTime(2021, 12, 10, 11, 30, 00); + public static Decimal? TransactionAmount1 = 100.00m; + + public static Decimal? TransactionAmount2 = 85.00m; + + public static Decimal SettledFeeAmount1 = 1.00m; + + public static Decimal SettledFeeAmount2 = 0.85m; + + public static DateTime SettledFeeDateTime1 = new DateTime(2021, 12, 17, 00, 00, 00); + + public static DateTime SettledFeeDateTime2 = new DateTime(2021, 12, 17, 01, 00, 00); + + public static Guid SettledFeeId1 = Guid.Parse("B4D429AE-756D-4F04-8941-4D41B1A75060"); + + public static Guid SettledFeeId2 = Guid.Parse("85C64CF1-6522-408D-93E3-D156B4D5C45B"); + + public static SettledFee SettledFee1 => new SettledFee(TestData.SettledFeeId1, TestData.TransactionId1, TestData.SettledFeeDateTime1, TestData.SettledFeeAmount1); + public static SettledFee SettledFee2 => new SettledFee(TestData.SettledFeeId2, TestData.TransactionId2, TestData.SettledFeeDateTime2, TestData.SettledFeeAmount2); + + public static Guid MessageId = Guid.Parse("353FB307-FDD5-41AE-A2AF-C927D57EADBB"); + + public static TransactionProcessor.Aggregates.Models.Transaction Transaction1 => new(TransactionId1, TransactionDateTime1, TransactionAmount1.Value); + public static TransactionProcessor.Aggregates.Models.Transaction Transaction2 => new(TransactionId2, TransactionDateTime2, TransactionAmount2.Value); + + public static DateTime StatementCreateDate = new DateTime(2021, 12, 10); + + public static DateTime StatementEmailedDate = new DateTime(2021, 12, 12); + + public static DateTime StatementGeneratedDate = new DateTime(2021, 12, 11); + public static Guid MerchantStatementId = Guid.Parse("C8CC622C-07D9-48E9-B544-F53BD29DE1E6"); + public static Guid EventId1 = Guid.Parse("C8CC622C-07D9-48E9-B544-F53BD29DE1E6"); + public static DataTransferObjects.Responses.Merchant.SettlementSchedule SettlementScheduleDTO = DataTransferObjects.Responses.Merchant.SettlementSchedule.Monthly; public static CreateMerchantRequest CreateMerchantRequest => new CreateMerchantRequest From 38893b50c87ba9eea69c9ac11d7fb5e467f896d3 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Thu, 13 Feb 2025 12:07:22 +0000 Subject: [PATCH 2/3] ready for PR i hope --- .../MerchantStatementAggregate.cs | 22 +- .../ContractDomainEventHandlerTests.cs | 4 +- .../EstateDomainEventHandlerTests.cs | 4 +- ...erchantStatementDomainEventHandlerTests.cs | 48 + .../OperatorDomainEventHandlerTests.cs | 4 +- .../Mediator/MediatorTests.cs | 5 + .../ContractDomainEventHandler.cs | 74 +- .../EventHandling/EstateDomainEventHandler.cs | 4 +- .../MerchantDomainEventHandler.cs | 25 - .../MerchantSettlementDomainEventHandler.cs | 46 + .../MerchantStatementDomainEventHandler.cs | 72 ++ .../OperatorDomainEventHandler.cs | 4 +- .../ReadModelDomainEventHandler.cs | 148 +++ .../StatementDomainEventHandler.cs | 70 ++ .../MerchantStatementRequestHandler.cs | 23 +- .../MerchantStatementDomainService.cs | 126 ++- .../ITransactionProcessorClient.cs | 15 +- .../TransactionProcessorClient.cs | 36 +- .../MerchantStatementDomainEvents.cs | 40 +- .../SpecflowExtensions.cs | 99 +- .../SubscriptionsHelper.cs | 23 +- .../TransactionProcessorSteps.cs | 75 +- .../Common/DockerHelper.cs | 3 - .../Features/SettlementReporting.feature | 167 ++++ .../Features/SettlementReporting.feature.cs | 875 ++++++++++++++++++ .../Shared/SharedSteps.cs | 40 + ...TransactionProcessorReadModelRepository.cs | 382 +++++++- .../TransactionProcessor.Repository.csproj | 2 + TransactionProcessor.Testing/TestData.cs | 20 + .../DomainEventHandlerRegistry.cs | 3 +- .../Bootstrapper/DomainServiceRegistry.cs | 1 + .../Bootstrapper/MediatorRegistry.cs | 8 + .../Bootstrapper/MiddlewareRegistry.cs | 3 +- .../Bootstrapper/RepositoryRegistry.cs | 1 + .../Controllers/EstateController.cs | 2 - .../Controllers/MerchantController.cs | 22 + .../Controllers/SettlementController.cs | 9 +- .../Controllers/TransactionController.cs | 3 +- .../Controllers/VoucherController.cs | 10 +- .../TransactionProcessor.csproj | 14 +- TransactionProcessor/appsettings.json | 236 +++-- 41 files changed, 2388 insertions(+), 380 deletions(-) create mode 100644 TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs create mode 100644 TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs create mode 100644 TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs create mode 100644 TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs create mode 100644 TransactionProcessor.BusinessLogic/EventHandling/StatementDomainEventHandler.cs create mode 100644 TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature create mode 100644 TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature.cs diff --git a/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs b/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs index c2c4a6f2..f1bc76a5 100644 --- a/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs +++ b/TransactionProcessor.Aggregates/MerchantStatementAggregate.cs @@ -19,8 +19,8 @@ public static void AddSettledFeeToStatement(this MerchantStatementAggregate aggr // Create statement id required aggregate.CreateStatement(statementId, createdDate, estateId, merchantId); - SettledFeeAddedToStatementEvent settledFeeAddedToStatementEvent = - new SettledFeeAddedToStatementEvent(aggregate.AggregateId, + MerchantStatementDomainEvents.SettledFeeAddedToStatementEvent settledFeeAddedToStatementEvent = + new MerchantStatementDomainEvents.SettledFeeAddedToStatementEvent(aggregate.AggregateId, eventId, aggregate.EstateId, aggregate.MerchantId, @@ -42,7 +42,7 @@ public static void AddTransactionToStatement(this MerchantStatementAggregate agg // Create statement id required aggregate.CreateStatement(statementId, createdDate, estateId, merchantId); - TransactionAddedToStatementEvent transactionAddedToStatementEvent = new TransactionAddedToStatementEvent(aggregate.AggregateId, + MerchantStatementDomainEvents.TransactionAddedToStatementEvent transactionAddedToStatementEvent = new MerchantStatementDomainEvents.TransactionAddedToStatementEvent(aggregate.AggregateId, eventId, aggregate.EstateId, aggregate.MerchantId, @@ -65,7 +65,7 @@ private static void CreateStatement(this MerchantStatementAggregate aggregate, Guard.ThrowIfInvalidGuid(estateId, nameof(estateId)); Guard.ThrowIfInvalidGuid(merchantId, nameof(merchantId)); - StatementCreatedEvent statementCreatedEvent = new StatementCreatedEvent(statementId, estateId, merchantId, createdDate); + MerchantStatementDomainEvents.StatementCreatedEvent statementCreatedEvent = new MerchantStatementDomainEvents.StatementCreatedEvent(statementId, estateId, merchantId, createdDate); aggregate.ApplyAndAppend(statementCreatedEvent); } @@ -78,7 +78,7 @@ public static void EmailStatement(this MerchantStatementAggregate aggregate, aggregate.EnsureStatementHasBeenCreated(); aggregate.EnsureStatementHasBeenGenerated(); - StatementEmailedEvent statementEmailedEvent = new StatementEmailedEvent(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, emailedDateTime, messageId); + MerchantStatementDomainEvents.StatementEmailedEvent statementEmailedEvent = new MerchantStatementDomainEvents.StatementEmailedEvent(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, emailedDateTime, messageId); aggregate.ApplyAndAppend(statementEmailedEvent); } @@ -93,7 +93,7 @@ public static void GenerateStatement(this MerchantStatementAggregate aggregate, throw new InvalidOperationException("Statement has no transactions or settled fees"); } - StatementGeneratedEvent statementGeneratedEvent = new StatementGeneratedEvent(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, generatedDateTime); + MerchantStatementDomainEvents.StatementGeneratedEvent statementGeneratedEvent = new MerchantStatementDomainEvents.StatementGeneratedEvent(aggregate.AggregateId, aggregate.EstateId, aggregate.MerchantId, generatedDateTime); aggregate.ApplyAndAppend(statementGeneratedEvent); } @@ -164,7 +164,7 @@ private static void EnsureStatementHasNotAlreadyBeenGenerated(this MerchantState } } - public static void PlayEvent(this MerchantStatementAggregate aggregate, StatementCreatedEvent domainEvent) + public static void PlayEvent(this MerchantStatementAggregate aggregate, MerchantStatementDomainEvents.StatementCreatedEvent domainEvent) { aggregate.IsCreated = true; aggregate.EstateId = domainEvent.EstateId; @@ -172,7 +172,7 @@ public static void PlayEvent(this MerchantStatementAggregate aggregate, Statemen aggregate.CreatedDateTime = domainEvent.DateCreated; } - public static void PlayEvent(this MerchantStatementAggregate aggregate, TransactionAddedToStatementEvent domainEvent) + public static void PlayEvent(this MerchantStatementAggregate aggregate, MerchantStatementDomainEvents.TransactionAddedToStatementEvent domainEvent) { aggregate.EstateId = domainEvent.EstateId; aggregate.MerchantId = domainEvent.MerchantId; @@ -180,7 +180,7 @@ public static void PlayEvent(this MerchantStatementAggregate aggregate, Transact aggregate.Transactions.Add(new Transaction(domainEvent.TransactionId, domainEvent.TransactionDateTime, domainEvent.TransactionValue)); } - public static void PlayEvent(this MerchantStatementAggregate aggregate, SettledFeeAddedToStatementEvent domainEvent) + public static void PlayEvent(this MerchantStatementAggregate aggregate, MerchantStatementDomainEvents.SettledFeeAddedToStatementEvent domainEvent) { aggregate.EstateId = domainEvent.EstateId; aggregate.MerchantId = domainEvent.MerchantId; @@ -188,13 +188,13 @@ public static void PlayEvent(this MerchantStatementAggregate aggregate, SettledF aggregate.SettledFees.Add(new SettledFee(domainEvent.SettledFeeId, domainEvent.TransactionId, domainEvent.SettledDateTime, domainEvent.SettledValue)); } - public static void PlayEvent(this MerchantStatementAggregate aggregate, StatementGeneratedEvent domainEvent) + public static void PlayEvent(this MerchantStatementAggregate aggregate, MerchantStatementDomainEvents.StatementGeneratedEvent domainEvent) { aggregate.IsGenerated = true; aggregate.GeneratedDateTime = domainEvent.DateGenerated; } - public static void PlayEvent(this MerchantStatementAggregate aggregate, StatementEmailedEvent domainEvent) + public static void PlayEvent(this MerchantStatementAggregate aggregate, MerchantStatementDomainEvents.StatementEmailedEvent domainEvent) { aggregate.HasBeenEmailed = true; aggregate.EmailedDateTime = domainEvent.DateEmailed; diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/ContractDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/ContractDomainEventHandlerTests.cs index b46ddb9a..2588611b 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/ContractDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/ContractDomainEventHandlerTests.cs @@ -16,11 +16,11 @@ public class ContractDomainEventHandlerTests private Mock EstateReportingRepository; - private ContractDomainEventHandler DomainEventHandler; + private ReadModelDomainEventHandler DomainEventHandler; public ContractDomainEventHandlerTests() { Logger.Initialise(NullLogger.Instance); this.EstateReportingRepository= new Mock(); - this.DomainEventHandler = new ContractDomainEventHandler(this.EstateReportingRepository.Object); + this.DomainEventHandler = new ReadModelDomainEventHandler(this.EstateReportingRepository.Object); } [Fact] diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/EstateDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/EstateDomainEventHandlerTests.cs index cc8435a3..13c47a0d 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/EstateDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/EstateDomainEventHandlerTests.cs @@ -18,14 +18,14 @@ public class EstateDomainEventHandlerTests private Mock EstateReportingRepository; - private EstateDomainEventHandler DomainEventHandler; + private ReadModelDomainEventHandler DomainEventHandler; public EstateDomainEventHandlerTests() { Logger.Initialise(NullLogger.Instance); this.EstateReportingRepository = new Mock(); - this.DomainEventHandler = new EstateDomainEventHandler(this.EstateReportingRepository.Object); + this.DomainEventHandler = new ReadModelDomainEventHandler(this.EstateReportingRepository.Object); } [Fact] public void EstateDomainEventHandler_EstateCreatedEvent_EventIsHandled() diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs new file mode 100644 index 00000000..8800ad18 --- /dev/null +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/MerchantStatementDomainEventHandlerTests.cs @@ -0,0 +1,48 @@ +using SimpleResults; +using TransactionProcessor.BusinessLogic.EventHandling; +using TransactionProcessor.Testing; + +namespace EstateManagement.BusinessLogic.Tests.EventHandling; + +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Moq; +using Shared.Logger; +using Shouldly; +using Xunit; + +public class MerchantStatementDomainEventHandlerTests +{ + private Mock Mediator; + + private MerchantStatementDomainEventHandler DomainEventHandler; + public MerchantStatementDomainEventHandlerTests() + { + Logger.Initialise(NullLogger.Instance); + this.Mediator = new Mock(); + this.DomainEventHandler = new MerchantStatementDomainEventHandler(this.Mediator.Object); + } + + [Fact] + public async Task MerchantStatementDomainEventHandler_Handle_StatementGeneratedEvent_EventIsHandled() + { + this.Mediator.Setup(m => m.Send(It.IsAny>(), It.IsAny())).ReturnsAsync(Result.Success()); + Should.NotThrow(async () => + { + await this.DomainEventHandler.Handle(TestData.DomainEvents.StatementGeneratedEvent, CancellationToken.None); + }); + this.Mediator.Verify(m=> m.Send(It.IsAny>(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task MerchantStatementDomainEventHandler_Handle_TransactionHasBeenCompletedEvent_EventIsHandled() + { + this.Mediator.Setup(m => m.Send(It.IsAny>(), It.IsAny())).ReturnsAsync(Result.Success()); + Should.NotThrow(async () => + { + await this.DomainEventHandler.Handle(TestData.TransactionHasBeenCompletedEvent, CancellationToken.None); + }); + this.Mediator.Verify(m => m.Send(It.IsAny>(), It.IsAny()), Times.Once); + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs index 5cbbe38a..5297825f 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs @@ -13,13 +13,13 @@ namespace TransactionProcessor.BusinessLogic.Tests.DomainEventHandlers; public class OperatorDomainEventHandlerTests { private Mock EstateReportingRepository; - private OperatorDomainEventHandler DomainEventHandler; + private ReadModelDomainEventHandler DomainEventHandler; public OperatorDomainEventHandlerTests() { Logger.Initialise(NullLogger.Instance); this.EstateReportingRepository = new Mock(); - this.DomainEventHandler = new OperatorDomainEventHandler(this.EstateReportingRepository.Object); + this.DomainEventHandler = new ReadModelDomainEventHandler(this.EstateReportingRepository.Object); } [Fact] diff --git a/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs b/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs index 21da3fc4..8f51f3b6 100644 --- a/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/Mediator/MediatorTests.cs @@ -93,6 +93,11 @@ public MediatorTests() this.Requests.Add(TestData.Queries.GetMerchantContractsQuery); this.Requests.Add(TestData.Queries.GetTransactionFeesForProductQuery); + // Merchant Statement Commands + this.Requests.Add(TestData.Commands.GenerateMerchantStatementCommand); + this.Requests.Add(TestData.Commands.AddTransactionToMerchantStatementCommand); + this.Requests.Add(TestData.Commands.EmailMerchantStatementCommand); + this.Requests.Add(TestData.Commands.AddSettledFeeToMerchantStatementCommand); } [Fact] diff --git a/TransactionProcessor.BusinessLogic/EventHandling/ContractDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/ContractDomainEventHandler.cs index 58635700..4a18109c 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/ContractDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/ContractDomainEventHandler.cs @@ -1,8 +1,9 @@ -using System.Threading; +/*using System.Threading; using System.Threading.Tasks; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.EventHandling; using SimpleResults; +using TransactionProcessor.BusinessLogic.Events; using TransactionProcessor.DomainEvents; using TransactionProcessor.Repository; @@ -42,63 +43,22 @@ public ContractDomainEventHandler(ITransactionProcessorReadModelRepository estat public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) { - return await this.HandleSpecificDomainEvent((dynamic)domainEvent, cancellationToken); - } - - /// - /// Handles the specific domain event. - /// - /// The domain event. - /// The cancellation token. - private async Task HandleSpecificDomainEvent(ContractDomainEvents.ContractCreatedEvent domainEvent, - CancellationToken cancellationToken) - { - return await this.EstateReportingRepository.AddContract(domainEvent, cancellationToken); - } - - /// - /// Handles the specific domain event. - /// - /// The domain event. - /// The cancellation token. - private async Task HandleSpecificDomainEvent(ContractDomainEvents.FixedValueProductAddedToContractEvent domainEvent, - CancellationToken cancellationToken) - { - return await this.EstateReportingRepository.AddContractProduct(domainEvent, cancellationToken); - } - - /// - /// Handles the specific domain event. - /// - /// The domain event. - /// The cancellation token. - private async Task HandleSpecificDomainEvent(ContractDomainEvents.VariableValueProductAddedToContractEvent domainEvent, - CancellationToken cancellationToken) - { - return await this.EstateReportingRepository.AddContractProduct(domainEvent, cancellationToken); - } + //return await this.HandleSpecificDomainEvent((dynamic)domainEvent, cancellationToken); + Task t = domainEvent switch + { + //MerchantDomainEvents.MerchantCreatedEvent de => this.EstateReportingRepository.AddMerchant(de, cancellationToken), + ContractDomainEvents.ContractCreatedEvent de => this.EstateReportingRepository.AddContract(de, cancellationToken), + ContractDomainEvents.FixedValueProductAddedToContractEvent de => this.EstateReportingRepository.AddContractProduct(de, cancellationToken), + ContractDomainEvents.VariableValueProductAddedToContractEvent de => this.EstateReportingRepository.AddContractProduct(de, cancellationToken), + ContractDomainEvents.TransactionFeeForProductAddedToContractEvent de => this.EstateReportingRepository.AddContractProductTransactionFee(de, cancellationToken), + ContractDomainEvents.TransactionFeeForProductDisabledEvent de => this.EstateReportingRepository.DisableContractProductTransactionFee(de, cancellationToken), + _ => null + }; + if (t != null) + return await t; - /// - /// Handles the specific domain event. - /// - /// The domain event. - /// The cancellation token. - private async Task HandleSpecificDomainEvent(ContractDomainEvents.TransactionFeeForProductAddedToContractEvent domainEvent, - CancellationToken cancellationToken) - { - return await this.EstateReportingRepository.AddContractProductTransactionFee(domainEvent, cancellationToken); - } - - /// - /// Handles the specific domain event. - /// - /// The domain event. - /// The cancellation token. - private async Task HandleSpecificDomainEvent(ContractDomainEvents.TransactionFeeForProductDisabledEvent domainEvent, - CancellationToken cancellationToken) - { - return await this.EstateReportingRepository.DisableContractProductTransactionFee(domainEvent, cancellationToken); + return Result.Success(); } #endregion -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/EventHandling/EstateDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/EstateDomainEventHandler.cs index 6acae78c..6ba7ba01 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/EstateDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/EstateDomainEventHandler.cs @@ -1,4 +1,4 @@ -using System.Threading; +/*using System.Threading; using System.Threading.Tasks; using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.EventHandling; @@ -86,5 +86,5 @@ private async Task HandleSpecificDomainEvent(EstateDomainEvents.EstateRe } #endregion -} +}*/ diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs index b2db9134..25960cb3 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantDomainEventHandler.cs @@ -51,32 +51,7 @@ public async Task Handle(IDomainEvent domainEvent, CancellationToken cancellationToken) { Task t = domainEvent switch{ - MerchantDomainEvents.MerchantCreatedEvent de => this.EstateReportingRepository.AddMerchant(de, cancellationToken), - MerchantDomainEvents.MerchantNameUpdatedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), - MerchantDomainEvents.AddressAddedEvent de => this.EstateReportingRepository.AddMerchantAddress(de, cancellationToken), - MerchantDomainEvents.ContactAddedEvent de => this.EstateReportingRepository.AddMerchantContact(de, cancellationToken), - MerchantDomainEvents.SecurityUserAddedToMerchantEvent de => this.EstateReportingRepository.AddMerchantSecurityUser(de, cancellationToken), - MerchantDomainEvents.DeviceAddedToMerchantEvent de => this.EstateReportingRepository.AddMerchantDevice(de, cancellationToken), - MerchantDomainEvents.OperatorAssignedToMerchantEvent de => this.EstateReportingRepository.AddMerchantOperator(de, cancellationToken), - MerchantDomainEvents.SettlementScheduleChangedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), - MerchantDomainEvents.MerchantReferenceAllocatedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), - //StatementGeneratedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), - TransactionDomainEvents.TransactionHasBeenCompletedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), - MerchantDomainEvents.ContractAddedToMerchantEvent de => this.EstateReportingRepository.AddContractToMerchant(de, cancellationToken), CallbackReceivedEnrichedEvent de => this.HandleSpecificDomainEvent(de, cancellationToken), - MerchantDomainEvents.DeviceSwappedForMerchantEvent de => this.EstateReportingRepository.SwapMerchantDevice(de, cancellationToken), - MerchantDomainEvents.OperatorRemovedFromMerchantEvent de => this.EstateReportingRepository.RemoveOperatorFromMerchant(de, cancellationToken), - MerchantDomainEvents.MerchantAddressLine1UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de,cancellationToken), - MerchantDomainEvents.MerchantAddressLine2UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantAddressLine3UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantAddressLine4UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantCountyUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantRegionUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantTownUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantPostalCodeUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), - MerchantDomainEvents.MerchantContactNameUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantContact(de, cancellationToken), - MerchantDomainEvents.MerchantContactEmailAddressUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantContact(de, cancellationToken), - MerchantDomainEvents.MerchantContactPhoneNumberUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantContact(de, cancellationToken), _ => null }; if (t != null) diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs new file mode 100644 index 00000000..c0604e71 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantSettlementDomainEventHandler.cs @@ -0,0 +1,46 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.EventHandling; +using SimpleResults; +using TransactionProcessor.BusinessLogic.Requests; +using TransactionProcessor.DomainEvents; + +namespace TransactionProcessor.BusinessLogic.EventHandling; + +public class MerchantSettlementDomainEventHandler : IDomainEventHandler +{ + private readonly IMediator Mediator; + + public MerchantSettlementDomainEventHandler(IMediator mediator) { + this.Mediator = mediator; + } + + public async Task Handle(IDomainEvent domainEvent, + CancellationToken cancellationToken) + { + Task t = domainEvent switch + { + SettlementDomainEvents.MerchantFeeSettledEvent mfse => this.HandleSpecificDomainEvent(mfse, cancellationToken), + _ => null + }; + if (t != null) + return await t; + + return Result.Success(); + } + + private async Task HandleSpecificDomainEvent(SettlementDomainEvents.MerchantFeeSettledEvent domainEvent, + CancellationToken cancellationToken) + { + MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand command = new(domainEvent.EstateId, + domainEvent.MerchantId, + domainEvent.FeeCalculatedDateTime, + domainEvent.CalculatedValue, + domainEvent.TransactionId, + domainEvent.FeeId); + + return await this.Mediator.Send(command, cancellationToken); + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs new file mode 100644 index 00000000..00fdb021 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/EventHandling/MerchantStatementDomainEventHandler.cs @@ -0,0 +1,72 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.EventHandling; +using SimpleResults; +using TransactionProcessor.BusinessLogic.Requests; +using TransactionProcessor.DomainEvents; + +namespace TransactionProcessor.BusinessLogic.EventHandling +{ + public class MerchantStatementDomainEventHandler : IDomainEventHandler + { + #region Fields + + /// + /// The mediator + /// + private readonly IMediator Mediator; + + #endregion + + #region Constructors + + public MerchantStatementDomainEventHandler(IMediator mediator) + { + this.Mediator = mediator; + } + + #endregion + + #region Methods + + /// + /// Handles the specified domain event. + /// + /// The domain event. + /// The cancellation token. + public async Task Handle(IDomainEvent domainEvent, + CancellationToken cancellationToken) + { + return await this.HandleSpecificDomainEvent((dynamic)domainEvent, cancellationToken); + } + + /// + /// Handles the specific domain event. + /// + /// The domain event. + /// The cancellation token. + private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken) { + MerchantStatementCommands.EmailMerchantStatementCommand command = new(domainEvent.EstateId, + domainEvent.MerchantId, domainEvent.MerchantStatementId); + + return await this.Mediator.Send(command, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(TransactionDomainEvents.TransactionHasBeenCompletedEvent domainEvent, + CancellationToken cancellationToken) { + MerchantStatementCommands.AddTransactionToMerchantStatementCommand command = new(domainEvent.EstateId, + domainEvent.MerchantId, + domainEvent.CompletedDateTime, + domainEvent.TransactionAmount, + domainEvent.IsAuthorised, + domainEvent.TransactionId); + + return await this.Mediator.Send(command, cancellationToken); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/EventHandling/OperatorDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/OperatorDomainEventHandler.cs index f46b950b..aca20a8a 100644 --- a/TransactionProcessor.BusinessLogic/EventHandling/OperatorDomainEventHandler.cs +++ b/TransactionProcessor.BusinessLogic/EventHandling/OperatorDomainEventHandler.cs @@ -1,4 +1,4 @@ -using Shared.DomainDrivenDesign.EventSourcing; +/*using Shared.DomainDrivenDesign.EventSourcing; using Shared.EventStore.EventHandling; using SimpleResults; using System; @@ -55,4 +55,4 @@ public async Task Handle(IDomainEvent domainEvent, #endregion } -} +}*/ diff --git a/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs new file mode 100644 index 00000000..1509eb91 --- /dev/null +++ b/TransactionProcessor.BusinessLogic/EventHandling/ReadModelDomainEventHandler.cs @@ -0,0 +1,148 @@ +using Shared.EventStore.EventHandling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Shared.DomainDrivenDesign.EventSourcing; +using SimpleResults; +using TransactionProcessor.Repository; +using TransactionProcessor.DomainEvents; +using static TransactionProcessor.DomainEvents.FloatDomainEvents; + +namespace TransactionProcessor.BusinessLogic.EventHandling +{ + public class ReadModelDomainEventHandler : IDomainEventHandler + { + private readonly ITransactionProcessorReadModelRepository EstateReportingRepository; + + public ReadModelDomainEventHandler(ITransactionProcessorReadModelRepository estateReportingRepository) { + this.EstateReportingRepository = estateReportingRepository; + } + + public async Task Handle(IDomainEvent domainEvent, + CancellationToken cancellationToken) { + Task task = domainEvent switch + { + EstateDomainEvents.EstateCreatedEvent de => this.HandleSpecificDomainEvent(de, cancellationToken), + EstateDomainEvents.SecurityUserAddedToEstateEvent de => this.EstateReportingRepository.AddEstateSecurityUser(de, cancellationToken), + EstateDomainEvents.EstateReferenceAllocatedEvent de => this.EstateReportingRepository.UpdateEstate(de, cancellationToken), + + OperatorDomainEvents.OperatorCreatedEvent de => this.EstateReportingRepository.AddOperator(de, cancellationToken), + OperatorDomainEvents.OperatorNameUpdatedEvent de => this.EstateReportingRepository.UpdateOperator(de, cancellationToken), + OperatorDomainEvents.OperatorRequireCustomMerchantNumberChangedEvent de => this.EstateReportingRepository.UpdateOperator(de, cancellationToken), + OperatorDomainEvents.OperatorRequireCustomTerminalNumberChangedEvent de => this.EstateReportingRepository.UpdateOperator(de, cancellationToken), + + ContractDomainEvents.ContractCreatedEvent de => this.EstateReportingRepository.AddContract(de, cancellationToken), + ContractDomainEvents.FixedValueProductAddedToContractEvent de => this.EstateReportingRepository.AddContractProduct(de, cancellationToken), + ContractDomainEvents.VariableValueProductAddedToContractEvent de => this.EstateReportingRepository.AddContractProduct(de, cancellationToken), + ContractDomainEvents.TransactionFeeForProductAddedToContractEvent de => this.EstateReportingRepository.AddContractProductTransactionFee(de, cancellationToken), + ContractDomainEvents.TransactionFeeForProductDisabledEvent de => this.EstateReportingRepository.DisableContractProductTransactionFee(de, cancellationToken), + + MerchantDomainEvents.MerchantCreatedEvent de => this.EstateReportingRepository.AddMerchant(de, cancellationToken), + MerchantDomainEvents.MerchantReferenceAllocatedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), + MerchantDomainEvents.AddressAddedEvent de => this.EstateReportingRepository.AddMerchantAddress(de, cancellationToken), + MerchantDomainEvents.ContactAddedEvent de => this.EstateReportingRepository.AddMerchantContact(de, cancellationToken), + MerchantDomainEvents.SecurityUserAddedToMerchantEvent de => this.EstateReportingRepository.AddMerchantSecurityUser(de, cancellationToken), + MerchantDomainEvents.DeviceAddedToMerchantEvent de => this.EstateReportingRepository.AddMerchantDevice(de, cancellationToken), + MerchantDomainEvents.DeviceSwappedForMerchantEvent de => this.EstateReportingRepository.SwapMerchantDevice(de, cancellationToken), + MerchantDomainEvents.OperatorAssignedToMerchantEvent de => this.EstateReportingRepository.AddMerchantOperator(de, cancellationToken), + MerchantDomainEvents.OperatorRemovedFromMerchantEvent de => this.EstateReportingRepository.RemoveOperatorFromMerchant(de, cancellationToken), + MerchantDomainEvents.SettlementScheduleChangedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), + MerchantDomainEvents.ContractAddedToMerchantEvent de => this.EstateReportingRepository.AddContractToMerchant(de, cancellationToken), + MerchantDomainEvents.ContractRemovedFromMerchantEvent de => this.EstateReportingRepository.RemoveContractFromMerchant(de, cancellationToken), + MerchantDomainEvents.MerchantNameUpdatedEvent de => this.EstateReportingRepository.UpdateMerchant(de, cancellationToken), + MerchantDomainEvents.MerchantAddressLine1UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantAddressLine2UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantAddressLine3UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantAddressLine4UpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantCountyUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantRegionUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantTownUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantPostalCodeUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantAddress(de, cancellationToken), + MerchantDomainEvents.MerchantContactNameUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantContact(de, cancellationToken), + MerchantDomainEvents.MerchantContactEmailAddressUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantContact(de, cancellationToken), + MerchantDomainEvents.MerchantContactPhoneNumberUpdatedEvent de => this.EstateReportingRepository.UpdateMerchantContact(de, cancellationToken), + + TransactionDomainEvents.TransactionHasStartedEvent de => this.EstateReportingRepository.StartTransaction(de, cancellationToken), + TransactionDomainEvents.AdditionalRequestDataRecordedEvent de => this.HandleSpecificDomainEvent(de, cancellationToken), + TransactionDomainEvents.AdditionalResponseDataRecordedEvent de => this.EstateReportingRepository.RecordTransactionAdditionalResponseData(de, cancellationToken), + TransactionDomainEvents.TransactionHasBeenLocallyAuthorisedEvent de => this.EstateReportingRepository.UpdateTransactionAuthorisation(de, cancellationToken), + TransactionDomainEvents.TransactionHasBeenLocallyDeclinedEvent de => this.EstateReportingRepository.UpdateTransactionAuthorisation(de, cancellationToken), + TransactionDomainEvents.TransactionAuthorisedByOperatorEvent de => this.EstateReportingRepository.UpdateTransactionAuthorisation(de, cancellationToken), + TransactionDomainEvents.TransactionDeclinedByOperatorEvent de => this.EstateReportingRepository.UpdateTransactionAuthorisation(de, cancellationToken), + TransactionDomainEvents.TransactionSourceAddedToTransactionEvent de => this.EstateReportingRepository.AddSourceDetailsToTransaction(de, cancellationToken), + TransactionDomainEvents.ProductDetailsAddedToTransactionEvent de => this.EstateReportingRepository.AddProductDetailsToTransaction(de, cancellationToken), + TransactionDomainEvents.TransactionHasBeenCompletedEvent de => this.EstateReportingRepository.CompleteTransaction(de, cancellationToken), + ReconciliationDomainEvents.ReconciliationHasStartedEvent de => this.EstateReportingRepository.StartReconciliation(de, cancellationToken), + ReconciliationDomainEvents.OverallTotalsRecordedEvent de => this.EstateReportingRepository.UpdateReconciliationOverallTotals(de, cancellationToken), + ReconciliationDomainEvents.ReconciliationHasBeenLocallyAuthorisedEvent de => this.EstateReportingRepository.UpdateReconciliationStatus(de, cancellationToken), + ReconciliationDomainEvents.ReconciliationHasBeenLocallyDeclinedEvent de => this.EstateReportingRepository.UpdateReconciliationStatus(de, cancellationToken), + ReconciliationDomainEvents.ReconciliationHasCompletedEvent de => this.EstateReportingRepository.CompleteReconciliation(de, cancellationToken), + VoucherDomainEvents.VoucherGeneratedEvent de => this.EstateReportingRepository.AddGeneratedVoucher(de, cancellationToken), + VoucherDomainEvents.VoucherIssuedEvent de => this.EstateReportingRepository.UpdateVoucherIssueDetails(de, cancellationToken), + VoucherDomainEvents.VoucherFullyRedeemedEvent de => this.EstateReportingRepository.UpdateVoucherRedemptionDetails(de, cancellationToken), + + FileProcessor.FileImportLog.DomainEvents.ImportLogCreatedEvent de => this.EstateReportingRepository.AddFileImportLog(de, cancellationToken), + FileProcessor.FileImportLog.DomainEvents.FileAddedToImportLogEvent de => this.EstateReportingRepository.AddFileToImportLog(de, cancellationToken), + + FileProcessor.File.DomainEvents.FileCreatedEvent de => this.EstateReportingRepository.AddFile(de, cancellationToken), + FileProcessor.File.DomainEvents.FileLineAddedEvent de => this.EstateReportingRepository.AddFileLineToFile(de, cancellationToken), + FileProcessor.File.DomainEvents.FileLineProcessingSuccessfulEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), + FileProcessor.File.DomainEvents.FileLineProcessingFailedEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), + FileProcessor.File.DomainEvents.FileLineProcessingIgnoredEvent de => this.EstateReportingRepository.UpdateFileLine(de, cancellationToken), + FileProcessor.File.DomainEvents.FileProcessingCompletedEvent de => this.EstateReportingRepository.UpdateFileAsComplete(de, cancellationToken), + + MerchantStatementDomainEvents.StatementCreatedEvent de => this.EstateReportingRepository.CreateStatement(de, cancellationToken), + MerchantStatementDomainEvents.TransactionAddedToStatementEvent de => this.EstateReportingRepository.AddTransactionToStatement(de, cancellationToken), + MerchantStatementDomainEvents.SettledFeeAddedToStatementEvent de => this.EstateReportingRepository.AddSettledFeeToStatement(de, cancellationToken), + MerchantStatementDomainEvents.StatementGeneratedEvent de => this.HandleSpecificDomainEvent(de, cancellationToken), + + FloatCreatedForContractProductEvent de => this.EstateReportingRepository.CreateFloat(de, cancellationToken), + FloatCreditPurchasedEvent de => this.EstateReportingRepository.CreateFloatActivity(de, cancellationToken), + FloatDecreasedByTransactionEvent de => this.EstateReportingRepository.CreateFloatActivity(de, cancellationToken), + + SettlementDomainEvents.SettlementCreatedForDateEvent de => this.EstateReportingRepository.CreateSettlement(de, cancellationToken), + SettlementDomainEvents.SettlementProcessingStartedEvent de => this.EstateReportingRepository.MarkSettlementAsProcessingStarted(de, cancellationToken), + SettlementDomainEvents.MerchantFeeAddedPendingSettlementEvent de => this.EstateReportingRepository.AddPendingMerchantFeeToSettlement(de, cancellationToken), + TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent de => this.EstateReportingRepository.AddSettledMerchantFeeToSettlement(de, cancellationToken), + SettlementDomainEvents.MerchantFeeSettledEvent de => this.EstateReportingRepository.MarkMerchantFeeAsSettled(de, cancellationToken), + SettlementDomainEvents.SettlementCompletedEvent de => this.EstateReportingRepository.MarkSettlementAsCompleted(de, cancellationToken), + + _ => Task.FromResult(Result.Success()) + }; + + return await task; + } + + private async Task HandleSpecificDomainEvent(TransactionDomainEvents.AdditionalRequestDataRecordedEvent domainEvent, + CancellationToken cancellationToken) + { + var result = await this.EstateReportingRepository.RecordTransactionAdditionalRequestData(domainEvent, cancellationToken); + if (result.IsFailed) + return result; + return await this.EstateReportingRepository.SetTransactionAmount(domainEvent, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(EstateDomainEvents.EstateCreatedEvent domainEvent, + CancellationToken cancellationToken) + { + Result createResult = await this.EstateReportingRepository.CreateReadModel(domainEvent, cancellationToken); + if (createResult.IsFailed) + return createResult; + + return await this.EstateReportingRepository.AddEstate(domainEvent, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken) + { + Result createResult = await this.EstateReportingRepository.MarkStatementAsGenerated(domainEvent, cancellationToken); + if (createResult.IsFailed) + return createResult; + + return await this.EstateReportingRepository.UpdateMerchant(domainEvent, cancellationToken); + } + } +} diff --git a/TransactionProcessor.BusinessLogic/EventHandling/StatementDomainEventHandler.cs b/TransactionProcessor.BusinessLogic/EventHandling/StatementDomainEventHandler.cs new file mode 100644 index 00000000..5ca3e94d --- /dev/null +++ b/TransactionProcessor.BusinessLogic/EventHandling/StatementDomainEventHandler.cs @@ -0,0 +1,70 @@ +/*using System.Threading; +using System.Threading.Tasks; +using Shared.DomainDrivenDesign.EventSourcing; +using Shared.EventStore.EventHandling; +using SimpleResults; +using TransactionProcessor.DomainEvents; +using TransactionProcessor.ProjectionEngine.Repository; +using TransactionProcessor.Repository; + +namespace TransactionProcessor.BusinessLogic.EventHandling +{ + public class StatementDomainEventHandler : IDomainEventHandler + { + #region Fields + + /// + /// The estate reporting repository + /// + private readonly ITransactionProcessorReadModelRepository EstateReportingRepository; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The estate reporting repository. + public StatementDomainEventHandler(ITransactionProcessorReadModelRepository estateReportingRepository) + { + this.EstateReportingRepository = estateReportingRepository; + } + + #endregion + + #region Methods + + public async Task Handle(IDomainEvent domainEvent, + CancellationToken cancellationToken) + { + return await this.HandleSpecificDomainEvent((dynamic)domainEvent, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.StatementCreatedEvent domainEvent, + CancellationToken cancellationToken) + { + return await this.EstateReportingRepository.CreateStatement(domainEvent, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.TransactionAddedToStatementEvent domainEvent, + CancellationToken cancellationToken) + { + return await this.EstateReportingRepository.AddTransactionToStatement(domainEvent, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.SettledFeeAddedToStatementEvent domainEvent, + CancellationToken cancellationToken) + { + return await this.EstateReportingRepository.AddSettledFeeToStatement(domainEvent, cancellationToken); + } + + private async Task HandleSpecificDomainEvent(MerchantStatementDomainEvents.StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken) + { + return await this.EstateReportingRepository.MarkStatementAsGenerated(domainEvent, cancellationToken); + } + #endregion + } +} +*/ \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs b/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs index 76741269..31fd69b8 100644 --- a/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs +++ b/TransactionProcessor.BusinessLogic/RequestHandlers/MerchantStatementRequestHandler.cs @@ -1,21 +1,12 @@ -using TransactionProcessor.BusinessLogic.Requests; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using SimpleResults; +using TransactionProcessor.BusinessLogic.Requests; +using TransactionProcessor.BusinessLogic.Services; -namespace EstateManagement.BusinessLogic.RequestHandlers +namespace TransactionProcessor.BusinessLogic.RequestHandlers { - using System; - using System.Threading; - using System.Threading.Tasks; - using MediatR; - using Services; - using SimpleResults; - using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; - - /// - /// - /// - /// - /// - /// public class MerchantStatementRequestHandler : IRequestHandler, IRequestHandler, IRequestHandler, diff --git a/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs b/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs index 2a4aa256..bd33a6ca 100644 --- a/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/MerchantStatementDomainService.cs @@ -16,10 +16,15 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using SecurityService.DataTransferObjects.Responses; +using Shared.Logger; using TransactionProcessor.Aggregates.Models; using TransactionProcessor.Aggregates; +using TransactionProcessor.BusinessLogic.Common; using TransactionProcessor.BusinessLogic.Requests; using TransactionProcessor.Database.Entities; +using TransactionProcessor.Repository; +using Transaction = TransactionProcessor.Aggregates.Models.Transaction; namespace TransactionProcessor.BusinessLogic.Services { @@ -54,9 +59,9 @@ public class MerchantStatementDomainService : IMerchantStatementDomainService private readonly IAggregateRepository EstateAggregateRepository; - private readonly ITransactionRe EstateManagementRepository; + private readonly ITransactionProcessorReadModelRepository EstateManagementRepository; - private readonly IStatementBuilder StatementBuilder; + //private readonly IStatementBuilder StatementBuilder; private readonly IMessagingServiceClient MessagingServiceClient; @@ -64,7 +69,7 @@ public class MerchantStatementDomainService : IMerchantStatementDomainService private readonly IFileSystem FileSystem; - private readonly IPDFGenerator PdfGenerator; + //private readonly IPDFGenerator PdfGenerator; #endregion @@ -83,21 +88,21 @@ public class MerchantStatementDomainService : IMerchantStatementDomainService /// The PDF generator. public MerchantStatementDomainService(IAggregateRepository merchantAggregateRepository, IAggregateRepository merchantStatementAggregateRepository, - IEstateManagementRepository estateManagementRepository, - IStatementBuilder statementBuilder, + ITransactionProcessorReadModelRepository estateManagementRepository, + //IStatementBuilder statementBuilder, IMessagingServiceClient messagingServiceClient, ISecurityServiceClient securityServiceClient, - IFileSystem fileSystem, - IPDFGenerator pdfGenerator) + IFileSystem fileSystem)//, + //IPDFGenerator pdfGenerator) { this.MerchantAggregateRepository = merchantAggregateRepository; this.MerchantStatementAggregateRepository = merchantStatementAggregateRepository; this.EstateManagementRepository = estateManagementRepository; - this.StatementBuilder = statementBuilder; + //this.StatementBuilder = statementBuilder; this.MessagingServiceClient = messagingServiceClient; this.SecurityServiceClient = securityServiceClient; this.FileSystem = fileSystem; - this.PdfGenerator = pdfGenerator; + //this.PdfGenerator = pdfGenerator; } #endregion @@ -164,13 +169,7 @@ public async Task AddSettledFeeToStatement(MerchantStatementCommands.Add Result result = await ApplyUpdates( async (MerchantStatementAggregate merchantStatementAggregate) => { - SettledFee settledFee = new SettledFee - { - DateTime = command.SettledDateTime, - Amount = command.SettledAmount, - TransactionId = command.TransactionId, - SettledFeeId = settlementFeeId - }; + SettledFee settledFee = new SettledFee(settlementFeeId, command.TransactionId, command.SettledDateTime, command.SettledAmount); Guid eventId = IdGenerationService.GenerateEventId(new { @@ -237,50 +236,50 @@ public async Task EmailStatement(MerchantStatementCommands.EmailMerchant Result result = await ApplyUpdates( async (MerchantStatementAggregate merchantStatementAggregate) => { - StatementHeader statementHeader = await this.EstateManagementRepository.GetStatement(command.EstateId, command.MerchantStatementId, cancellationToken); - - String html = await this.StatementBuilder.GetStatementHtml(statementHeader, cancellationToken); - - String base64 = await this.PdfGenerator.CreatePDF(html, cancellationToken); - - SendEmailRequest sendEmailRequest = new SendEmailRequest - { - Body = "Please find attached this months statement.", - ConnectionIdentifier = command.EstateId, - FromAddress = "golfhandicapping@btinternet.com", // TODO: lookup from config - IsHtml = true, - Subject = $"Merchant Statement for {statementHeader.StatementDate}", - // MessageId = command.MerchantStatementId, - ToAddresses = new List - { - statementHeader.MerchantEmail - }, - EmailAttachments = new List - { - new EmailAttachment - { - FileData = base64, - FileType = FileType.PDF, - Filename = $"merchantstatement{statementHeader.StatementDate}.pdf" - } - } - }; - - Guid messageId = IdGenerationService.GenerateEventId(new - { - command.MerchantStatementId, - DateTime.Now - }); - - sendEmailRequest.MessageId = messageId; - - this.TokenResponse = await Helpers.GetToken(this.TokenResponse, this.SecurityServiceClient, cancellationToken); - - var sendEmailResponseResult = await this.MessagingServiceClient.SendEmail(this.TokenResponse.AccessToken, sendEmailRequest, cancellationToken); - //if (sendEmailResponseResult.IsFailed) { - // // TODO: record a failed event?? - //} - merchantStatementAggregate.EmailStatement(DateTime.Now, messageId); + //StatementHeader statementHeader = await this.EstateManagementRepository.GetStatement(command.EstateId, command.MerchantStatementId, cancellationToken); + + //String html = await this.StatementBuilder.GetStatementHtml(statementHeader, cancellationToken); + + //String base64 = await this.PdfGenerator.CreatePDF(html, cancellationToken); + + //SendEmailRequest sendEmailRequest = new SendEmailRequest + //{ + // Body = "Please find attached this months statement.", + // ConnectionIdentifier = command.EstateId, + // FromAddress = "golfhandicapping@btinternet.com", // TODO: lookup from config + // IsHtml = true, + // Subject = $"Merchant Statement for {statementHeader.StatementDate}", + // // MessageId = command.MerchantStatementId, + // ToAddresses = new List + // { + // statementHeader.MerchantEmail + // }, + // EmailAttachments = new List + // { + // new EmailAttachment + // { + // FileData = base64, + // FileType = FileType.PDF, + // Filename = $"merchantstatement{statementHeader.StatementDate}.pdf" + // } + // } + //}; + + //Guid messageId = IdGenerationService.GenerateEventId(new + //{ + // command.MerchantStatementId, + // DateTime.Now + //}); + + //sendEmailRequest.MessageId = messageId; + + //this.TokenResponse = await Helpers.GetToken(this.TokenResponse, this.SecurityServiceClient, cancellationToken); + + //var sendEmailResponseResult = await this.MessagingServiceClient.SendEmail(this.TokenResponse.AccessToken, sendEmailRequest, cancellationToken); + ////if (sendEmailResponseResult.IsFailed) { + //// // TODO: record a failed event?? + ////} + //merchantStatementAggregate.EmailStatement(DateTime.Now, messageId); return Result.Success(); }, @@ -315,12 +314,7 @@ public async Task AddTransactionToStatement(MerchantStatementCommands.Ad async (MerchantStatementAggregate merchantStatementAggregate) => { // Add transaction to statement - Models.MerchantStatement.Transaction transaction = new() - { - DateTime = command.TransactionDateTime, - Amount = command.TransactionAmount.GetValueOrDefault(0), - TransactionId = command.TransactionId - }; + Transaction transaction = new(command.TransactionId, command.TransactionDateTime, command.TransactionAmount.GetValueOrDefault(0)); Guid eventId = IdGenerationService.GenerateEventId(new { diff --git a/TransactionProcessor.Client/ITransactionProcessorClient.cs b/TransactionProcessor.Client/ITransactionProcessorClient.cs index 9e4de499..45b4a80e 100644 --- a/TransactionProcessor.Client/ITransactionProcessorClient.cs +++ b/TransactionProcessor.Client/ITransactionProcessorClient.cs @@ -20,7 +20,6 @@ namespace TransactionProcessor.Client public interface ITransactionProcessorClient { #region Methods - Task> PerformTransaction(String accessToken, SerialisedMessage transactionRequest, CancellationToken cancellationToken); @@ -240,18 +239,18 @@ Task MakeMerchantWithdrawal(String accessToken, MakeMerchantWithdrawalRequest makeMerchantWithdrawalRequest, CancellationToken cancellationToken); - Task> GetSettlement(String accessToken, + Task> GetSettlement(String accessToken, Guid estateId, Guid? merchantId, Guid settlementId, CancellationToken cancellationToken); - Task>> GetSettlements(String accessToken, - Guid estateId, - Guid? merchantId, - String startDate, - String endDate, - CancellationToken cancellationToken); + Task>> GetSettlements(String accessToken, + Guid estateId, + Guid? merchantId, + String startDate, + String endDate, + CancellationToken cancellationToken); Task>> GetMerchants(String accessToken, Guid estateId, diff --git a/TransactionProcessor.Client/TransactionProcessorClient.cs b/TransactionProcessor.Client/TransactionProcessorClient.cs index fb8e10ef..c4e8aed0 100644 --- a/TransactionProcessor.Client/TransactionProcessorClient.cs +++ b/TransactionProcessor.Client/TransactionProcessorClient.cs @@ -252,7 +252,7 @@ public async Task> GetSettlementByDate(String accessT Guid estateId, Guid merchantId, CancellationToken cancellationToken) { - String requestUri = this.BuildRequestUrl($"/api/settlements/{settlementDate.Date:yyyy-MM-dd}/estates/{estateId}/merchants/{merchantId}/pending"); + String requestUri = this.BuildRequestUrl($"/api/estates/{estateId}/settlements/{settlementDate.Date:yyyy-MM-dd}/merchants/{merchantId}/pending"); try { // Add the access token to the client headers this.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -384,7 +384,7 @@ public async Task ProcessSettlement(String accessToken, Guid estateId, Guid merchantId, CancellationToken cancellationToken) { - String requestUri = this.BuildRequestUrl($"/api/settlements/{settlementDate.Date:yyyy-MM-dd}/estates/{estateId}/merchants/{merchantId}"); + String requestUri = this.BuildRequestUrl($"/api/estates/{estateId}/settlements/{settlementDate.Date:yyyy-MM-dd}/merchants/{merchantId}"); try { StringContent httpContent = new(String.Empty, Encoding.UTF8, "application/json"); @@ -1256,12 +1256,12 @@ public async Task>> GetMerchants(String accessToke return response; } - public async Task> GetSettlement(String accessToken, - Guid estateId, - Guid? merchantId, - Guid settlementId, - CancellationToken cancellationToken) { - SettlementResponse response = null; + public async Task> GetSettlement(String accessToken, + Guid estateId, + Guid? merchantId, + Guid settlementId, + CancellationToken cancellationToken) { + DataTransferObjects.Responses.Settlement.SettlementResponse response = null; String requestUri = this.BuildRequestUrl($"/api/estates/{estateId}/settlements/{settlementId}?merchantId={merchantId}"); @@ -1278,7 +1278,7 @@ public async Task> GetSettlement(String accessToken, if (result.IsFailed) return ResultHelpers.CreateFailure(result); - ResponseData responseData = this.HandleResponseContent(result.Data); + ResponseData responseData = this.HandleResponseContent(result.Data); return Result.Success(responseData.Data); } @@ -1292,15 +1292,15 @@ public async Task> GetSettlement(String accessToken, return response; } - public async Task>> GetSettlements(String accessToken, - Guid estateId, - Guid? merchantId, - String startDate, - String endDate, - CancellationToken cancellationToken) { - List response = null; + public async Task>> GetSettlements(String accessToken, + Guid estateId, + Guid? merchantId, + String startDate, + String endDate, + CancellationToken cancellationToken) { + List response = null; - String requestUri = this.BuildRequestUrl($"/api/estates/{estateId}/settlements/?merchantId={merchantId}&start_date={startDate}&end_date={endDate}"); + String requestUri = this.BuildRequestUrl($"/api/estates/{estateId}/settlements?merchantId={merchantId}&start_date={startDate}&end_date={endDate}"); try { // Add the access token to the client headers @@ -1315,7 +1315,7 @@ public async Task>> GetSettlements(String access if (result.IsFailed) return ResultHelpers.CreateFailure(result); - ResponseData> responseData = this.HandleResponseContent>(result.Data); + ResponseData> responseData = this.HandleResponseContent>(result.Data); return Result.Success(responseData.Data); } diff --git a/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs b/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs index 743d9364..9018b072 100644 --- a/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs +++ b/TransactionProcessor.DomainEvents/MerchantStatementDomainEvents.cs @@ -1,37 +1,17 @@ using Shared.DomainDrivenDesign.EventSourcing; +using System.Diagnostics.CodeAnalysis; -namespace TransactionProcessor.DomainEvents -{ - public record SettledFeeAddedToStatementEvent(Guid MerchantStatementId, - Guid EventId, - Guid EstateId, - Guid MerchantId, - Guid SettledFeeId, - Guid TransactionId, - DateTime SettledDateTime, - Decimal SettledValue) : DomainEvent(MerchantStatementId, EventId); +namespace TransactionProcessor.DomainEvents { + [ExcludeFromCodeCoverage] + public class MerchantStatementDomainEvents { + public record SettledFeeAddedToStatementEvent(Guid MerchantStatementId, Guid EventId, Guid EstateId, Guid MerchantId, Guid SettledFeeId, Guid TransactionId, DateTime SettledDateTime, Decimal SettledValue) : DomainEvent(MerchantStatementId, EventId); - public record TransactionAddedToStatementEvent(Guid MerchantStatementId, - Guid EventId, - Guid EstateId, - Guid MerchantId, - Guid TransactionId, - DateTime TransactionDateTime, - Decimal TransactionValue) : DomainEvent(MerchantStatementId, EventId); + public record TransactionAddedToStatementEvent(Guid MerchantStatementId, Guid EventId, Guid EstateId, Guid MerchantId, Guid TransactionId, DateTime TransactionDateTime, Decimal TransactionValue) : DomainEvent(MerchantStatementId, EventId); - public record StatementGeneratedEvent(Guid MerchantStatementId, - Guid EstateId, - Guid MerchantId, - DateTime DateGenerated) : DomainEvent(MerchantStatementId, Guid.NewGuid()); + public record StatementGeneratedEvent(Guid MerchantStatementId, Guid EstateId, Guid MerchantId, DateTime DateGenerated) : DomainEvent(MerchantStatementId, Guid.NewGuid()); - public record StatementEmailedEvent(Guid MerchantStatementId, - Guid EstateId, - Guid MerchantId, - DateTime DateEmailed, - Guid MessageId) : DomainEvent(MerchantStatementId, Guid.NewGuid()); + public record StatementEmailedEvent(Guid MerchantStatementId, Guid EstateId, Guid MerchantId, DateTime DateEmailed, Guid MessageId) : DomainEvent(MerchantStatementId, Guid.NewGuid()); - public record StatementCreatedEvent(Guid MerchantStatementId, - Guid EstateId, - Guid MerchantId, - DateTime DateCreated) : DomainEvent(MerchantStatementId, Guid.NewGuid()); + public record StatementCreatedEvent(Guid MerchantStatementId, Guid EstateId, Guid MerchantId, DateTime DateCreated) : DomainEvent(MerchantStatementId, Guid.NewGuid()); + } } \ No newline at end of file diff --git a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs index 2c0cc319..310ba9d4 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/SpecflowExtensions.cs @@ -1,4 +1,5 @@ -using Shouldly; +using Shared.Extensions; +using Shouldly; using TransactionProcessor.DataTransferObjects.Requests.Contract; using TransactionProcessor.DataTransferObjects.Requests.Estate; using TransactionProcessor.DataTransferObjects.Requests.Merchant; @@ -14,6 +15,7 @@ namespace TransactionProcessor.IntegrationTesting.Helpers; using Reqnroll; using AssignOperatorToMerchantRequest = DataTransferObjects.Requests.Merchant.AssignOperatorRequest; using AssignOperatorToEstateRequest = DataTransferObjects.Requests.Estate.AssignOperatorRequest; +using Shared.General; public static class ReqnrollTableHelper { @@ -98,6 +100,101 @@ public static T GetEnumValue(DataTableRow row, } public static class ReqnrollExtensions{ + public class SettlementDetails + { + public Guid EstateId { get; set; } + public Guid MerchantId { get; set; } + public DateTime SettlementDate { get; set; } + public Int32 NumberOfFeesSettled { get; set; } + public Decimal ValueOfFeesSettled { get; set; } + public Boolean IsCompleted { get; set; } + } + + public class SettlementFeeDetails + { + public Guid EstateId { get; set; } + public Guid MerchantId { get; set; } + public Guid SettlementId { get; set; } + public String FeeDescription { get; set; } + public Boolean IsSettled { get; set; } + public String Operator { get; set; } + public Decimal CalculatedValue { get; set; } + } + + public static SettlementDetails ToSettlementDetails(this DataTableRows tableRows, string estateName, List estateDetailsList) + { + SettlementDetails result = new SettlementDetails(); + + EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); + estateDetails.ShouldNotBeNull(); + result.EstateId = estateDetails.EstateId; + + foreach (DataTableRow tableRow in tableRows) + { + result.SettlementDate = ReqnrollTableHelper.GetDateForDateString(ReqnrollTableHelper.GetStringRowValue(tableRow, "SettlementDate"), DateTime.UtcNow.Date); + result.NumberOfFeesSettled = ReqnrollTableHelper.GetIntValue(tableRow, "NumberOfFeesSettled"); + result.ValueOfFeesSettled = ReqnrollTableHelper.GetDecimalValue(tableRow, "ValueOfFeesSettled"); + result.IsCompleted = ReqnrollTableHelper.GetBooleanValue(tableRow, "IsCompleted"); + } + + return result; + } + + public static Guid CalculateSettlementAggregateId(DateTime settlementDate, + Guid merchantId, + Guid estateId) + { + Guid aggregateId = GuidCalculator.Combine(estateId, merchantId, settlementDate.ToGuid()); + return aggregateId; + } + + public static SettlementDetails ToSettlementDetails(this DataTableRows tableRows, string estateName, + string merchantName, List estateDetailsList) + { + SettlementDetails result = new SettlementDetails(); + + EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); + estateDetails.ShouldNotBeNull(); + result.EstateId = estateDetails.EstateId; + // Lookup the merchant id + result.MerchantId = estateDetails.GetMerchant(merchantName).MerchantId; + + foreach (DataTableRow tableRow in tableRows) + { + result.SettlementDate = ReqnrollTableHelper.GetDateForDateString(ReqnrollTableHelper.GetStringRowValue(tableRow, "SettlementDate"), DateTime.UtcNow.Date); + result.NumberOfFeesSettled = ReqnrollTableHelper.GetIntValue(tableRow, "NumberOfFeesSettled"); + result.ValueOfFeesSettled = ReqnrollTableHelper.GetDecimalValue(tableRow, "ValueOfFeesSettled"); + result.IsCompleted = ReqnrollTableHelper.GetBooleanValue(tableRow, "IsCompleted"); + } + + return result; + } + + public static List ToSettlementFeeDetails(this DataTableRows tableRows, string estateName, + string merchantName, + String settlementDateString, + List estateDetailsList) + { + List settlementFeeDetailsList = new List(); + EstateDetails estateDetails = estateDetailsList.SingleOrDefault(e => e.EstateName == estateName); + estateDetails.ShouldNotBeNull(); + + foreach (DataTableRow tableRow in tableRows) + { + SettlementFeeDetails settlementFeeDetails = new SettlementFeeDetails(); + DateTime settlementDate = ReqnrollTableHelper.GetDateForDateString(settlementDateString, DateTime.UtcNow.Date); + Guid settlementId = ReqnrollExtensions.CalculateSettlementAggregateId(settlementDate, estateDetails.GetMerchant(merchantName).MerchantId, estateDetails.EstateId); + settlementFeeDetails.SettlementId = settlementId; + settlementFeeDetails.EstateId = estateDetails.EstateId; + settlementFeeDetails.MerchantId = estateDetails.GetMerchant(merchantName).MerchantId; + settlementFeeDetails.FeeDescription = ReqnrollTableHelper.GetStringRowValue(tableRow, "FeeDescription"); + settlementFeeDetails.IsSettled = ReqnrollTableHelper.GetBooleanValue(tableRow, "IsSettled"); + settlementFeeDetails.Operator = ReqnrollTableHelper.GetStringRowValue(tableRow, "Operator"); + settlementFeeDetails.CalculatedValue = ReqnrollTableHelper.GetDecimalValue(tableRow, "CalculatedValue"); + } + return settlementFeeDetailsList; + } + public static List<(EstateDetails, MerchantResponse, Guid, Address)> ToAddressUpdates(this DataTableRows tableRows, List estateDetailsList) { diff --git a/TransactionProcessor.IntegrationTesting.Helpers/SubscriptionsHelper.cs b/TransactionProcessor.IntegrationTesting.Helpers/SubscriptionsHelper.cs index 65093e53..f2bf5478 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/SubscriptionsHelper.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/SubscriptionsHelper.cs @@ -9,9 +9,15 @@ public static class SubscriptionsHelper ("$ce-EstateAggregate", "Transaction Processor", 2), ("$ce-SettlementAggregate", "Transaction Processor", 0), ("$ce-TransactionAggregate", "Transaction Processor", 0), - ("$ce-OperatorAggregate", "Transaction Processor", 0), ("$ce-VoucherAggregate", "Transaction Processor", 0), + ("$ce-OperatorAggregate", "Transaction Processor", 0), ("$ce-ContractAggregate", "Transaction Processor", 0), ("$ce-MerchantAggregate", "Transaction Processor", 0), + ("$ce-CallbackMessageAggregate", "Transaction Processor", 0), + ("$ce-FileAggregate", "Transaction Processor", 0), + ("$ce-FloatAggregate", "Transaction Processor", 0), + ("$ce-MerchantStatementAggregate", "Transaction Processor", 0), + ("$ce-ReconciliationAggregate", "Transaction Processor", 0), + ("$ce-VoucherAggregate", "Transaction Processor", 0), // Ordered ("$ce-EstateAggregate", "Transaction Processor - Ordered", 2), @@ -19,20 +25,9 @@ public static class SubscriptionsHelper ("$ce-MerchantDepositListAggregate", "Transaction Processor - Ordered", 2), ("$ce-TransactionAggregate", "Transaction Processor - Ordered", 2), ("$ce-VoucherAggregate", "Transaction Processor - Ordered", 2), - - // Estate Management Main - ("$ce-CallbackMessageAggregate", "Estate Management", 0), - ("$ce-FileAggregate", "Estate Management", 0), - ("$ce-FloatAggregate", "Estate Management", 0), - ("$ce-MerchantStatementAggregate", "Estate Management", 0), - ("$ce-ReconciliationAggregate", "Estate Management", 0), - ("$ce-SettlementAggregate", "Estate Management", 0), - ("$ce-TransactionAggregate", "Estate Management", 0), - ("$ce-VoucherAggregate", "Estate Management", 0), + ("$ce-MerchantStatementAggregate", "Transaction Processor - Ordered", 0) + - // Estate Management Ordered - ("$ce-MerchantStatementAggregate", "Estate Management - Ordered", 0), - ("$ce-TransactionAggregate", "Estate Management - Ordered", 0), }; return subscriptions; diff --git a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs index 9ab0cb51..3dcec8ba 100644 --- a/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs +++ b/TransactionProcessor.IntegrationTesting.Helpers/TransactionProcessorSteps.cs @@ -7,6 +7,7 @@ using TransactionProcessor.DataTransferObjects.Responses.Contract; using TransactionProcessor.DataTransferObjects.Responses.Estate; using TransactionProcessor.DataTransferObjects.Responses.Operator; +using TransactionProcessor.DataTransferObjects.Responses.Settlement; namespace TransactionProcessor.IntegrationTesting.Helpers; @@ -34,6 +35,76 @@ public TransactionProcessorSteps(ITransactionProcessorClient transactionProcesso this.TestHostHttpClient = testHostHttpClient; this.ProjectionManagementClient = projectionManagementClient; } + + public async Task WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheStartDateAndTheEndDateTheFollowingDataIsReturned(String accessToken, DateTime stateDate, DateTime endDate, ReqnrollExtensions.SettlementDetails expectedSettlementDetails) + { + await Retry.For(async () => { + Result>? settlementList = + await this.TransactionProcessorClient.GetSettlements(accessToken, + expectedSettlementDetails.EstateId, + expectedSettlementDetails.MerchantId, + stateDate.ToString("yyyyMMdd"), + endDate.ToString("yyyyMMdd"), + CancellationToken.None); + + settlementList.Data.ShouldNotBeNull(); + settlementList.Data.ShouldNotBeEmpty(); + + DataTransferObjects.Responses.Settlement.SettlementResponse? settlement = + settlementList.Data.SingleOrDefault(s => s.SettlementDate == expectedSettlementDetails.SettlementDate && + s.NumberOfFeesSettled == expectedSettlementDetails.NumberOfFeesSettled && + s.ValueOfFeesSettled == expectedSettlementDetails.ValueOfFeesSettled && s.IsCompleted == expectedSettlementDetails.IsCompleted); + + settlement.ShouldNotBeNull(); + }, + TimeSpan.FromMinutes(2)); + } + + public async Task WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheDateTheFollowingFeesAreSettled(String accessToken, List settlementFeeDetailsList) + { + var settlements = settlementFeeDetailsList.DistinctBy(d => new { + d.EstateId, + d.MerchantId, + d.SettlementId + }).Select(s => new { + s.EstateId, + s.MerchantId, + s.SettlementId + }); + + foreach (var settlementFeeDetails in settlements) + { + await Retry.For(async () => { + Result? settlement = + await this.TransactionProcessorClient.GetSettlement(accessToken, + settlementFeeDetails.EstateId, + settlementFeeDetails.MerchantId, + settlementFeeDetails.SettlementId, + CancellationToken.None); + + settlement.ShouldNotBeNull(); + + settlement.Data.SettlementFees.ShouldNotBeNull(); + settlement.Data.SettlementFees.ShouldNotBeEmpty(); + + var settlementFees = settlementFeeDetailsList.Where(s => s.EstateId == settlementFeeDetails.EstateId && + s.MerchantId == settlementFeeDetails.MerchantId && + s.SettlementId == settlementFeeDetails.SettlementId).ToList(); + + foreach (ReqnrollExtensions.SettlementFeeDetails feeDetails in settlementFees) + { + SettlementFeeResponse settlementFee = + settlement.Data.SettlementFees.SingleOrDefault(sf => sf.FeeDescription == feeDetails.FeeDescription && sf.IsSettled == feeDetails.IsSettled && + sf.OperatorIdentifier == feeDetails.Operator && + sf.CalculatedValue == feeDetails.CalculatedValue); + + settlementFee.ShouldNotBeNull(); + } + }, + TimeSpan.FromMinutes(3)); + } + } + public async Task WhenIGetTheMerchantsForThenMerchantsWillBeReturned(String accessToken, String estateName, List estateDetailsList, Int32 expectedMerchantCount) { Guid estateId = Guid.NewGuid(); @@ -355,6 +426,7 @@ await Retry.For(async () => { estate.EstateId, transactionResponse.TransactionId, CancellationToken.None); + voucher.ShouldNotBeNull(); }); return voucher; } @@ -373,6 +445,7 @@ await Retry.For(async () => RedeemVoucherResponse response = await this.TransactionProcessorClient .RedeemVoucher(accessToken, redeemVoucherRequest, CancellationToken.None) .ConfigureAwait(false); + response.ShouldNotBeNull(); response.RemainingBalance.ShouldBe(expectedBalance); }); } @@ -429,7 +502,7 @@ await this.TransactionProcessorClient.GetSettlementByDate(accessToken, request.Item1.EstateId, request.Item2, CancellationToken.None); - + settlements.ShouldNotBeNull(); settlements.NumberOfFeesPendingSettlement.ShouldBe(request.Item4, $"Settlement date {request.Item3}"); }, TimeSpan.FromMinutes(3)); diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs index 406691ed..c13bff14 100644 --- a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs +++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs @@ -105,10 +105,7 @@ public override ContainerBuilder SetupTransactionProcessorContainer(){ public override async Task CreateSubscriptions(){ List<(String streamName, String groupName, Int32 maxRetries)> subscriptions = new(); subscriptions.AddRange(MessagingService.IntegrationTesting.Helpers.SubscriptionsHelper.GetSubscriptions()); - //var estateSubscriptions = EstateManagement.IntegrationTesting.Helpers.SubscriptionsHelper.GetSubscriptions(); - //subscriptions.AddRange(estateSubscriptions.Where(e => e.streamName != "$ce-EstateAggregate")); subscriptions.AddRange(TransactionProcessor.IntegrationTesting.Helpers.SubscriptionsHelper.GetSubscriptions()); - foreach ((String streamName, String groupName, Int32 maxRetries) subscription in subscriptions) { var x = subscription; diff --git a/TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature b/TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature new file mode 100644 index 00000000..62066586 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature @@ -0,0 +1,167 @@ +@base @shared +Feature: SettlementReporting + +Background: + + Given I create the following api scopes + | Name | DisplayName | Description | + | estateManagement | Estate Managememt REST Scope | A scope for Estate Managememt REST | + | transactionProcessor | Transaction Processor REST Scope | A scope for Transaction Processor REST | + + Given the following api resources exist + | Name | DisplayName | Secret | Scopes | UserClaims | + | estateManagement | Estate Managememt REST | Secret1 | estateManagement | MerchantId, EstateId, role | + | transactionProcessor | Transaction Processor REST | Secret1 | transactionProcessor | | + + Given the following clients exist + | ClientId | ClientName | Secret | Scopes | GrantTypes | + | serviceClient | Service Client | Secret1 | estateManagement,transactionProcessor | client_credentials | + + Given I have a token to access the estate management and transaction processor resources + | ClientId | + | serviceClient | + + Given I have created the following estates + | EstateName | + | Test Estate 1 | + | Test Estate 2 | + + Given I have created the following operators + | EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber | + | Test Estate 1 | Safaricom | True | True | + | Test Estate 2 | Safaricom | True | True | + + And I have assigned the following operators to the estates + | EstateName | OperatorName | + | Test Estate 1 | Safaricom | + | Test Estate 2 | Safaricom | + + Given I create a contract with the following values + | EstateName | OperatorName | ContractDescription | + | Test Estate 1 | Safaricom | Safaricom Contract | + | Test Estate 2 | Safaricom | Safaricom Contract | + + When I create the following Products + | EstateName | OperatorName | ContractDescription | ProductName | DisplayText | Value | ProductType | + | Test Estate 1 | Safaricom | Safaricom Contract | Variable Topup | Custom | | MobileTopup | + | Test Estate 2 | Safaricom | Safaricom Contract | Variable Topup | Custom | | MobileTopup | + + When I add the following Transaction Fees + | EstateName | OperatorName | ContractDescription | ProductName | CalculationType | FeeDescription | Value | FeeType | + | Test Estate 1 | Safaricom | Safaricom Contract | Variable Topup | Percentage | Merchant Commission | 0.50 | Merchant | + | Test Estate 2 | Safaricom | Safaricom Contract | Variable Topup | Percentage | Merchant Commission | 0.85 | Merchant | + + Given I create the following merchants + | MerchantName | AddressLine1 | Town | Region | Country | ContactName | EmailAddress | EstateName | SettlementSchedule | + | Test Merchant 1 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 1 | testcontact1@merchant1.co.uk | Test Estate 1 | Weekly | + | Test Merchant 2 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 2 | testcontact2@merchant2.co.uk | Test Estate 1 | Weekly | + | Test Merchant 3 | Address Line 1 | TestTown | Test Region | United Kingdom | Test Contact 3 | testcontact3@merchant2.co.uk | Test Estate 2 | Monthly | + + Given I have assigned the following operator to the merchants + | OperatorName | MerchantName | MerchantNumber | TerminalNumber | EstateName | + | Safaricom | Test Merchant 1 | 00000001 | 10000001 | Test Estate 1 | + | Safaricom | Test Merchant 2 | 00000002 | 10000002 | Test Estate 1 | + | Safaricom | Test Merchant 3 | 00000003 | 10000003 | Test Estate 2 | + + Given I have assigned the following devices to the merchants + | DeviceIdentifier | MerchantName | EstateName | + | 123456780 | Test Merchant 1 | Test Estate 1 | + | 123456781 | Test Merchant 2 | Test Estate 1 | + | 123456782 | Test Merchant 3 | Test Estate 2 | + + When I add the following contracts to the following merchants + | EstateName | MerchantName | ContractDescription | + | Test Estate 1 | Test Merchant 1 | Safaricom Contract | + | Test Estate 1 | Test Merchant 2 | Safaricom Contract | + | Test Estate 2 | Test Merchant 3 | Safaricom Contract | + + Given I make the following manual merchant deposits + | Reference | Amount | DateTime | MerchantName | EstateName | + | Deposit1 | 50000.00 | Today | Test Merchant 1 | Test Estate 1 | + | Deposit1 | 50000.00 | Today | Test Merchant 2 | Test Estate 1 | + | Deposit1 | 50000.00 | Today | Test Merchant 3 | Test Estate 2 | + + When I perform the following transactions + | DateTime | TransactionNumber | TransactionType | TransactionSource | MerchantName | DeviceIdentifier | EstateName | OperatorName | TransactionAmount | CustomerAccountNumber | CustomerEmailAddress | ContractDescription | ProductName | + | 2022-01-06 | 1 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 100.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 2 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 5.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 3 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 25.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 4 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 150.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 5 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 3.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 6 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 40.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 7 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 60.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 8 | Sale | 1 | Test Merchant 1 | 123456780 | Test Estate 1 | Safaricom | 101.00 | 123456789 | | Safaricom Contract | Variable Topup | + + | 2022-01-06 | 1 | Sale | 1 | Test Merchant 2 | 123456781 | Test Estate 1 | Safaricom | 100.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 2 | Sale | 1 | Test Merchant 2 | 123456781 | Test Estate 1 | Safaricom | 5.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 3 | Sale | 1 | Test Merchant 2 | 123456781 | Test Estate 1 | Safaricom | 25.00 | 123456789 | | Safaricom Contract | Variable Topup | + | 2022-01-06 | 4 | Sale | 1 | Test Merchant 2 | 123456781 | Test Estate 1 | Safaricom | 15.00 | 123456789 | | Safaricom Contract | Variable Topup | + + | 2022-01-06 | 1 | Sale | 1 | Test Merchant 3 | 123456782 | Test Estate 2 | Safaricom | 100.00 | 123456789 | | Safaricom Contract | Variable Topup | + + Then transaction response should contain the following information + | EstateName | MerchantName | TransactionNumber | ResponseCode | ResponseMessage | + | Test Estate 1 | Test Merchant 1 | 1 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 2 | 1008 | DECLINED BY OPERATOR | + | Test Estate 1 | Test Merchant 1 | 3 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 4 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 5 | 1008 | DECLINED BY OPERATOR | + | Test Estate 1 | Test Merchant 1 | 6 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 7 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 1 | 8 | 0000 | SUCCESS | + + | Test Estate 1 | Test Merchant 2 | 1 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 2 | 2 | 1008 | DECLINED BY OPERATOR | + | Test Estate 1 | Test Merchant 2 | 3 | 0000 | SUCCESS | + | Test Estate 1 | Test Merchant 2 | 4 | 0000 | SUCCESS | + + | Test Estate 2 | Test Merchant 3 | 1 | 0000 | SUCCESS | + + When I get the pending settlements the following information should be returned + | SettlementDate | EstateName | MerchantName | NumberOfFees | + | 2022-01-13 | Test Estate 1 | Test Merchant 1 | 6 | + | 2022-01-13 | Test Estate 1 | Test Merchant 2 | 3 | + + When I process the settlement for '2022-01-13' on Estate 'Test Estate 1' for Merchant 'Test Merchant 1' then 6 fees are marked as settled and the settlement is completed + + When I process the settlement for '2022-01-13' on Estate 'Test Estate 1' for Merchant 'Test Merchant 2' then 3 fees are marked as settled and the settlement is completed + + When I get the pending settlements the following information should be returned + | SettlementDate | EstateName | MerchantName | NumberOfFees | + | 2022-02-06 | Test Estate 2 | Test Merchant 3 | 1 | + + When I process the settlement for '2022-02-06' on Estate 'Test Estate 2' for Merchant 'Test Merchant 3' then 1 fees are marked as settled and the settlement is completed + +@settlement +@PRTest +Scenario: Get Settlements - Merchant Filter + When I get the Estate Settlement Report for Estate 'Test Estate 1' for Merchant 'Test Merchant 1' with the Start Date '2022-01-13' and the End Date '2022-02-06' the following data is returned + | SettlementDate | NumberOfFeesSettled | ValueOfFeesSettled | IsCompleted | + | 2022-01-13 | 6 | 2.39 | True | + + When I get the Estate Settlement Report for Estate 'Test Estate 1' for Merchant 'Test Merchant 2' with the Start Date '2022-01-13' and the End Date '2022-02-06' the following data is returned + | SettlementDate | NumberOfFeesSettled | ValueOfFeesSettled | IsCompleted | + | 2022-01-13 | 3 | 0.71 | True | + + When I get the Estate Settlement Report for Estate 'Test Estate 2' for Merchant 'Test Merchant 3' with the Start Date '2022-01-13' and the End Date '2022-02-06' the following data is returned + | SettlementDate | NumberOfFeesSettled | ValueOfFeesSettled | IsCompleted | + | 2022-02-06 | 1 | 0.85 | True | + + When I get the Estate Settlement Report for Estate 'Test Estate 1' for Merchant 'Test Merchant 1' with the Date '2022-01-13' the following fees are settled + | FeeDescription | IsSettled | Operator | CalculatedValue | + | Merchant Commission | True | Safaricom | 0.50 | + | Merchant Commission | True | Safaricom | 0.13 | + | Merchant Commission | True | Safaricom | 0.75 | + | Merchant Commission | True | Safaricom | 0.20 | + | Merchant Commission | True | Safaricom | 0.30 | + | Merchant Commission | True | Safaricom | 0.51 | + + When I get the Estate Settlement Report for Estate 'Test Estate 1' for Merchant 'Test Merchant 2' with the Date '2022-01-13' the following fees are settled + | FeeDescription | IsSettled | Operator | CalculatedValue | + | Merchant Commission | True | Safaricom | 0.50 | + | Merchant Commission | True | Safaricom | 0.13 | + | Merchant Commission | True | Safaricom | 0.08 | + + When I get the Estate Settlement Report for Estate 'Test Estate 2' for Merchant 'Test Merchant 3' with the Date '2022-02-06' the following fees are settled + | FeeDescription | IsSettled | Operator | CalculatedValue | + | Merchant Commission | True | Safaricom | 0.85 | \ No newline at end of file diff --git a/TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature.cs b/TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature.cs new file mode 100644 index 00000000..3e4c83a2 --- /dev/null +++ b/TransactionProcessor.IntegrationTests/Features/SettlementReporting.feature.cs @@ -0,0 +1,875 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by Reqnroll (https://www.reqnroll.net/). +// Reqnroll Version:1.0.0.0 +// Reqnroll Generator Version:1.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace TransactionProcessor.IntegrationTests.Features +{ + using Reqnroll; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "1.0.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("SettlementReporting")] + [NUnit.Framework.CategoryAttribute("base")] + [NUnit.Framework.CategoryAttribute("shared")] + public partial class SettlementReportingFeature + { + + private Reqnroll.ITestRunner testRunner; + + private static string[] featureTags = new string[] { + "base", + "shared"}; + +#line 1 "SettlementReporting.feature" +#line hidden + + [NUnit.Framework.OneTimeSetUpAttribute()] + public virtual async System.Threading.Tasks.Task FeatureSetupAsync() + { + testRunner = Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(null, NUnit.Framework.TestContext.CurrentContext.WorkerId); + Reqnroll.FeatureInfo featureInfo = new Reqnroll.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "SettlementReporting", null, ProgrammingLanguage.CSharp, featureTags); + await testRunner.OnFeatureStartAsync(featureInfo); + } + + [NUnit.Framework.OneTimeTearDownAttribute()] + public virtual async System.Threading.Tasks.Task FeatureTearDownAsync() + { + await testRunner.OnFeatureEndAsync(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public async System.Threading.Tasks.Task TestInitializeAsync() + { + } + + [NUnit.Framework.TearDownAttribute()] + public async System.Threading.Tasks.Task TestTearDownAsync() + { + await testRunner.OnScenarioEndAsync(); + } + + public void ScenarioInitialize(Reqnroll.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); + } + + public async System.Threading.Tasks.Task ScenarioStartAsync() + { + await testRunner.OnScenarioStartAsync(); + } + + public async System.Threading.Tasks.Task ScenarioCleanupAsync() + { + await testRunner.CollectScenarioErrorsAsync(); + } + + public virtual async System.Threading.Tasks.Task FeatureBackgroundAsync() + { +#line 4 +#line hidden + Reqnroll.Table table213 = new Reqnroll.Table(new string[] { + "Name", + "DisplayName", + "Description"}); + table213.AddRow(new string[] { + "estateManagement", + "Estate Managememt REST Scope", + "A scope for Estate Managememt REST"}); + table213.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST Scope", + "A scope for Transaction Processor REST"}); +#line 6 + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table213, "Given "); +#line hidden + Reqnroll.Table table214 = new Reqnroll.Table(new string[] { + "Name", + "DisplayName", + "Secret", + "Scopes", + "UserClaims"}); + table214.AddRow(new string[] { + "estateManagement", + "Estate Managememt REST", + "Secret1", + "estateManagement", + "MerchantId, EstateId, role"}); + table214.AddRow(new string[] { + "transactionProcessor", + "Transaction Processor REST", + "Secret1", + "transactionProcessor", + ""}); +#line 11 + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table214, "Given "); +#line hidden + Reqnroll.Table table215 = new Reqnroll.Table(new string[] { + "ClientId", + "ClientName", + "Secret", + "Scopes", + "GrantTypes"}); + table215.AddRow(new string[] { + "serviceClient", + "Service Client", + "Secret1", + "estateManagement,transactionProcessor", + "client_credentials"}); +#line 16 + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table215, "Given "); +#line hidden + Reqnroll.Table table216 = new Reqnroll.Table(new string[] { + "ClientId"}); + table216.AddRow(new string[] { + "serviceClient"}); +#line 20 + await testRunner.GivenAsync("I have a token to access the estate management and transaction processor resource" + + "s", ((string)(null)), table216, "Given "); +#line hidden + Reqnroll.Table table217 = new Reqnroll.Table(new string[] { + "EstateName"}); + table217.AddRow(new string[] { + "Test Estate 1"}); + table217.AddRow(new string[] { + "Test Estate 2"}); +#line 24 + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table217, "Given "); +#line hidden + Reqnroll.Table table218 = new Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "RequireCustomMerchantNumber", + "RequireCustomTerminalNumber"}); + table218.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "True", + "True"}); + table218.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "True", + "True"}); +#line 29 + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table218, "Given "); +#line hidden + Reqnroll.Table table219 = new Reqnroll.Table(new string[] { + "EstateName", + "OperatorName"}); + table219.AddRow(new string[] { + "Test Estate 1", + "Safaricom"}); + table219.AddRow(new string[] { + "Test Estate 2", + "Safaricom"}); +#line 34 + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table219, "And "); +#line hidden + Reqnroll.Table table220 = new Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription"}); + table220.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract"}); + table220.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "Safaricom Contract"}); +#line 39 + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table220, "Given "); +#line hidden + Reqnroll.Table table221 = new Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "DisplayText", + "Value", + "ProductType"}); + table221.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Custom", + "", + "MobileTopup"}); + table221.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Custom", + "", + "MobileTopup"}); +#line 44 + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table221, "When "); +#line hidden + Reqnroll.Table table222 = new Reqnroll.Table(new string[] { + "EstateName", + "OperatorName", + "ContractDescription", + "ProductName", + "CalculationType", + "FeeDescription", + "Value", + "FeeType"}); + table222.AddRow(new string[] { + "Test Estate 1", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Percentage", + "Merchant Commission", + "0.50", + "Merchant"}); + table222.AddRow(new string[] { + "Test Estate 2", + "Safaricom", + "Safaricom Contract", + "Variable Topup", + "Percentage", + "Merchant Commission", + "0.85", + "Merchant"}); +#line 49 + await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table222, "When "); +#line hidden + Reqnroll.Table table223 = new Reqnroll.Table(new string[] { + "MerchantName", + "AddressLine1", + "Town", + "Region", + "Country", + "ContactName", + "EmailAddress", + "EstateName", + "SettlementSchedule"}); + table223.AddRow(new string[] { + "Test Merchant 1", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 1", + "testcontact1@merchant1.co.uk", + "Test Estate 1", + "Weekly"}); + table223.AddRow(new string[] { + "Test Merchant 2", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 2", + "testcontact2@merchant2.co.uk", + "Test Estate 1", + "Weekly"}); + table223.AddRow(new string[] { + "Test Merchant 3", + "Address Line 1", + "TestTown", + "Test Region", + "United Kingdom", + "Test Contact 3", + "testcontact3@merchant2.co.uk", + "Test Estate 2", + "Monthly"}); +#line 54 + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table223, "Given "); +#line hidden + Reqnroll.Table table224 = new Reqnroll.Table(new string[] { + "OperatorName", + "MerchantName", + "MerchantNumber", + "TerminalNumber", + "EstateName"}); + table224.AddRow(new string[] { + "Safaricom", + "Test Merchant 1", + "00000001", + "10000001", + "Test Estate 1"}); + table224.AddRow(new string[] { + "Safaricom", + "Test Merchant 2", + "00000002", + "10000002", + "Test Estate 1"}); + table224.AddRow(new string[] { + "Safaricom", + "Test Merchant 3", + "00000003", + "10000003", + "Test Estate 2"}); +#line 60 + await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table224, "Given "); +#line hidden + Reqnroll.Table table225 = new Reqnroll.Table(new string[] { + "DeviceIdentifier", + "MerchantName", + "EstateName"}); + table225.AddRow(new string[] { + "123456780", + "Test Merchant 1", + "Test Estate 1"}); + table225.AddRow(new string[] { + "123456781", + "Test Merchant 2", + "Test Estate 1"}); + table225.AddRow(new string[] { + "123456782", + "Test Merchant 3", + "Test Estate 2"}); +#line 66 + await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table225, "Given "); +#line hidden + Reqnroll.Table table226 = new Reqnroll.Table(new string[] { + "EstateName", + "MerchantName", + "ContractDescription"}); + table226.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "Safaricom Contract"}); + table226.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "Safaricom Contract"}); + table226.AddRow(new string[] { + "Test Estate 2", + "Test Merchant 3", + "Safaricom Contract"}); +#line 72 + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table226, "When "); +#line hidden + Reqnroll.Table table227 = new Reqnroll.Table(new string[] { + "Reference", + "Amount", + "DateTime", + "MerchantName", + "EstateName"}); + table227.AddRow(new string[] { + "Deposit1", + "50000.00", + "Today", + "Test Merchant 1", + "Test Estate 1"}); + table227.AddRow(new string[] { + "Deposit1", + "50000.00", + "Today", + "Test Merchant 2", + "Test Estate 1"}); + table227.AddRow(new string[] { + "Deposit1", + "50000.00", + "Today", + "Test Merchant 3", + "Test Estate 2"}); +#line 78 + await testRunner.GivenAsync("I make the following manual merchant deposits", ((string)(null)), table227, "Given "); +#line hidden + Reqnroll.Table table228 = new Reqnroll.Table(new string[] { + "DateTime", + "TransactionNumber", + "TransactionType", + "TransactionSource", + "MerchantName", + "DeviceIdentifier", + "EstateName", + "OperatorName", + "TransactionAmount", + "CustomerAccountNumber", + "CustomerEmailAddress", + "ContractDescription", + "ProductName"}); + table228.AddRow(new string[] { + "2022-01-06", + "1", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "100.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "2", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "5.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "3", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "25.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "4", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "150.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "5", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "3.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "6", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "40.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "7", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "60.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "8", + "Sale", + "1", + "Test Merchant 1", + "123456780", + "Test Estate 1", + "Safaricom", + "101.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "1", + "Sale", + "1", + "Test Merchant 2", + "123456781", + "Test Estate 1", + "Safaricom", + "100.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "2", + "Sale", + "1", + "Test Merchant 2", + "123456781", + "Test Estate 1", + "Safaricom", + "5.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "3", + "Sale", + "1", + "Test Merchant 2", + "123456781", + "Test Estate 1", + "Safaricom", + "25.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "4", + "Sale", + "1", + "Test Merchant 2", + "123456781", + "Test Estate 1", + "Safaricom", + "15.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); + table228.AddRow(new string[] { + "2022-01-06", + "1", + "Sale", + "1", + "Test Merchant 3", + "123456782", + "Test Estate 2", + "Safaricom", + "100.00", + "123456789", + "", + "Safaricom Contract", + "Variable Topup"}); +#line 84 + await testRunner.WhenAsync("I perform the following transactions", ((string)(null)), table228, "When "); +#line hidden + Reqnroll.Table table229 = new Reqnroll.Table(new string[] { + "EstateName", + "MerchantName", + "TransactionNumber", + "ResponseCode", + "ResponseMessage"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "1", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "2", + "1008", + "DECLINED BY OPERATOR"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "3", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "4", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "5", + "1008", + "DECLINED BY OPERATOR"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "6", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "7", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 1", + "8", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "1", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "2", + "1008", + "DECLINED BY OPERATOR"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "3", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 1", + "Test Merchant 2", + "4", + "0000", + "SUCCESS"}); + table229.AddRow(new string[] { + "Test Estate 2", + "Test Merchant 3", + "1", + "0000", + "SUCCESS"}); +#line 102 + await testRunner.ThenAsync("transaction response should contain the following information", ((string)(null)), table229, "Then "); +#line hidden + Reqnroll.Table table230 = new Reqnroll.Table(new string[] { + "SettlementDate", + "EstateName", + "MerchantName", + "NumberOfFees"}); + table230.AddRow(new string[] { + "2022-01-13", + "Test Estate 1", + "Test Merchant 1", + "6"}); + table230.AddRow(new string[] { + "2022-01-13", + "Test Estate 1", + "Test Merchant 2", + "3"}); +#line 120 + await testRunner.WhenAsync("I get the pending settlements the following information should be returned", ((string)(null)), table230, "When "); +#line hidden +#line 125 + await testRunner.WhenAsync("I process the settlement for \'2022-01-13\' on Estate \'Test Estate 1\' for Merchant " + + "\'Test Merchant 1\' then 6 fees are marked as settled and the settlement is comple" + + "ted", ((string)(null)), ((Reqnroll.Table)(null)), "When "); +#line hidden +#line 127 + await testRunner.WhenAsync("I process the settlement for \'2022-01-13\' on Estate \'Test Estate 1\' for Merchant " + + "\'Test Merchant 2\' then 3 fees are marked as settled and the settlement is comple" + + "ted", ((string)(null)), ((Reqnroll.Table)(null)), "When "); +#line hidden + Reqnroll.Table table231 = new Reqnroll.Table(new string[] { + "SettlementDate", + "EstateName", + "MerchantName", + "NumberOfFees"}); + table231.AddRow(new string[] { + "2022-02-06", + "Test Estate 2", + "Test Merchant 3", + "1"}); +#line 129 + await testRunner.WhenAsync("I get the pending settlements the following information should be returned", ((string)(null)), table231, "When "); +#line hidden +#line 133 + await testRunner.WhenAsync("I process the settlement for \'2022-02-06\' on Estate \'Test Estate 2\' for Merchant " + + "\'Test Merchant 3\' then 1 fees are marked as settled and the settlement is comple" + + "ted", ((string)(null)), ((Reqnroll.Table)(null)), "When "); +#line hidden + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Get Settlements - Merchant Filter")] + [NUnit.Framework.CategoryAttribute("settlement")] + [NUnit.Framework.CategoryAttribute("PRTest")] + public async System.Threading.Tasks.Task GetSettlements_MerchantFilter() + { + string[] tagsOfScenario = new string[] { + "settlement", + "PRTest"}; + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + Reqnroll.ScenarioInfo scenarioInfo = new Reqnroll.ScenarioInfo("Get Settlements - Merchant Filter", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 137 +this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + await this.ScenarioStartAsync(); +#line 4 +await this.FeatureBackgroundAsync(); +#line hidden + Reqnroll.Table table232 = new Reqnroll.Table(new string[] { + "SettlementDate", + "NumberOfFeesSettled", + "ValueOfFeesSettled", + "IsCompleted"}); + table232.AddRow(new string[] { + "2022-01-13", + "6", + "2.39", + "True"}); +#line 138 + await testRunner.WhenAsync("I get the Estate Settlement Report for Estate \'Test Estate 1\' for Merchant \'Test " + + "Merchant 1\' with the Start Date \'2022-01-13\' and the End Date \'2022-02-06\' the f" + + "ollowing data is returned", ((string)(null)), table232, "When "); +#line hidden + Reqnroll.Table table233 = new Reqnroll.Table(new string[] { + "SettlementDate", + "NumberOfFeesSettled", + "ValueOfFeesSettled", + "IsCompleted"}); + table233.AddRow(new string[] { + "2022-01-13", + "3", + "0.71", + "True"}); +#line 142 + await testRunner.WhenAsync("I get the Estate Settlement Report for Estate \'Test Estate 1\' for Merchant \'Test " + + "Merchant 2\' with the Start Date \'2022-01-13\' and the End Date \'2022-02-06\' the f" + + "ollowing data is returned", ((string)(null)), table233, "When "); +#line hidden + Reqnroll.Table table234 = new Reqnroll.Table(new string[] { + "SettlementDate", + "NumberOfFeesSettled", + "ValueOfFeesSettled", + "IsCompleted"}); + table234.AddRow(new string[] { + "2022-02-06", + "1", + "0.85", + "True"}); +#line 146 + await testRunner.WhenAsync("I get the Estate Settlement Report for Estate \'Test Estate 2\' for Merchant \'Test " + + "Merchant 3\' with the Start Date \'2022-01-13\' and the End Date \'2022-02-06\' the f" + + "ollowing data is returned", ((string)(null)), table234, "When "); +#line hidden + Reqnroll.Table table235 = new Reqnroll.Table(new string[] { + "FeeDescription", + "IsSettled", + "Operator", + "CalculatedValue"}); + table235.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.50"}); + table235.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.13"}); + table235.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.75"}); + table235.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.20"}); + table235.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.30"}); + table235.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.51"}); +#line 150 + await testRunner.WhenAsync("I get the Estate Settlement Report for Estate \'Test Estate 1\' for Merchant \'Test " + + "Merchant 1\' with the Date \'2022-01-13\' the following fees are settled", ((string)(null)), table235, "When "); +#line hidden + Reqnroll.Table table236 = new Reqnroll.Table(new string[] { + "FeeDescription", + "IsSettled", + "Operator", + "CalculatedValue"}); + table236.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.50"}); + table236.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.13"}); + table236.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.08"}); +#line 159 + await testRunner.WhenAsync("I get the Estate Settlement Report for Estate \'Test Estate 1\' for Merchant \'Test " + + "Merchant 2\' with the Date \'2022-01-13\' the following fees are settled", ((string)(null)), table236, "When "); +#line hidden + Reqnroll.Table table237 = new Reqnroll.Table(new string[] { + "FeeDescription", + "IsSettled", + "Operator", + "CalculatedValue"}); + table237.AddRow(new string[] { + "Merchant Commission", + "True", + "Safaricom", + "0.85"}); +#line 165 + await testRunner.WhenAsync("I get the Estate Settlement Report for Estate \'Test Estate 2\' for Merchant \'Test " + + "Merchant 3\' with the Date \'2022-02-06\' the following fees are settled", ((string)(null)), table237, "When "); +#line hidden + } + await this.ScenarioCleanupAsync(); + } + } +} +#pragma warning restore +#endregion diff --git a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs index 753b2aab..1f4ef3cd 100644 --- a/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs +++ b/TransactionProcessor.IntegrationTests/Shared/SharedSteps.cs @@ -606,5 +606,45 @@ await this.TransactionProcessorSteps.WhenIRemoveTheOperatorFromMerchantOnTheOper merchantName, operatorName); } + + [When(@"I get the Estate Settlement Report for Estate '([^']*)' with the Start Date '([^']*)' and the End Date '([^']*)' the following data is returned")] + public async Task WhenIGetTheEstateSettlementReportForEstateWithTheStartDateAndTheEndDateTheFollowingDataIsReturned(string estateName, + string startDateString, + string endDateString, + DataTable table) + { + DateTime stateDate = ReqnrollTableHelper.GetDateForDateString(startDateString, DateTime.UtcNow.Date); + DateTime endDate = ReqnrollTableHelper.GetDateForDateString(endDateString, DateTime.UtcNow.Date); + + ReqnrollExtensions.SettlementDetails settlementDetails = table.Rows.ToSettlementDetails(estateName, this.TestingContext.Estates); + await this.TransactionProcessorSteps.WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheStartDateAndTheEndDateTheFollowingDataIsReturned(this.TestingContext.AccessToken, stateDate, endDate, settlementDetails); + } + + [When(@"I get the Estate Settlement Report for Estate '([^']*)' for Merchant '([^']*)' with the Start Date '([^']*)' and the End Date '([^']*)' the following data is returned")] + public async Task WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheStartDateAndTheEndDateTheFollowingDataIsReturned(string estateName, + string merchantName, + string startDateString, + string endDateString, + DataTable table) + { + + DateTime stateDate = ReqnrollTableHelper.GetDateForDateString(startDateString, DateTime.UtcNow.Date); + DateTime endDate = ReqnrollTableHelper.GetDateForDateString(endDateString, DateTime.UtcNow.Date); + ReqnrollExtensions.SettlementDetails settlementDetails = table.Rows.ToSettlementDetails(estateName, merchantName, this.TestingContext.Estates); + await this.TransactionProcessorSteps.WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheStartDateAndTheEndDateTheFollowingDataIsReturned(this.TestingContext.AccessToken, + stateDate, endDate, + settlementDetails); + } + + [When(@"I get the Estate Settlement Report for Estate '([^']*)' for Merchant '([^']*)' with the Date '([^']*)' the following fees are settled")] + public async Task WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheDateTheFollowingFeesAreSettled(string estateName, + string merchantName, + string settlementDateString, + DataTable table) + { + + List settlementFeeDetailsList = table.Rows.ToSettlementFeeDetails(estateName, merchantName, settlementDateString, this.TestingContext.Estates); + await this.TransactionProcessorSteps.WhenIGetTheEstateSettlementReportForEstateForMerchantWithTheDateTheFollowingFeesAreSettled(this.TestingContext.AccessToken, settlementFeeDetailsList); + } } } diff --git a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs index 74584a2a..f09f26b0 100644 --- a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs +++ b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs @@ -13,6 +13,8 @@ using Shared.Results; using Microsoft.EntityFrameworkCore; using System.Reflection; +using FileProcessor.File.DomainEvents; +using FileProcessor.FileImportLog.DomainEvents; using TransactionProcessor.DomainEvents; using TransactionProcessor.Models.Contract; using MerchantModel = TransactionProcessor.Models.Merchant.Merchant; @@ -20,9 +22,12 @@ using ContractModel = TransactionProcessor.Models.Contract.Contract; using ContractProductTransactionFee = TransactionProcessor.Database.Entities.ContractProductTransactionFee; using static TransactionProcessor.DomainEvents.MerchantDomainEvents; +using static TransactionProcessor.DomainEvents.MerchantStatementDomainEvents; +using File = TransactionProcessor.Database.Entities.File; namespace TransactionProcessor.Repository { public interface ITransactionProcessorReadModelRepository { + Task> GetMerchantFromReference(Guid estateId, String reference, CancellationToken cancellationToken); @@ -59,20 +64,20 @@ Task AddEstate(EstateDomainEvents.EstateCreatedEvent domainEvent, Task AddEstateSecurityUser(EstateDomainEvents.SecurityUserAddedToEstateEvent domainEvent, CancellationToken cancellationToken); - //Task AddFile(FileCreatedEvent domainEvent, - // CancellationToken cancellationToken); + Task AddFile(FileCreatedEvent domainEvent, + CancellationToken cancellationToken); - //Task AddFileImportLog(ImportLogCreatedEvent domainEvent, - // CancellationToken cancellationToken); + Task AddFileImportLog(ImportLogCreatedEvent domainEvent, + CancellationToken cancellationToken); - //Task AddFileLineToFile(FileLineAddedEvent domainEvent, - // CancellationToken cancellationToken); + Task AddFileLineToFile(FileLineAddedEvent domainEvent, + CancellationToken cancellationToken); - //Task AddFileToImportLog(FileAddedToImportLogEvent domainEvent, - // CancellationToken cancellationToken); + Task AddFileToImportLog(FileAddedToImportLogEvent domainEvent, + CancellationToken cancellationToken); - //Task AddGeneratedVoucher(VoucherGeneratedEvent domainEvent, - // CancellationToken cancellationToken); + Task AddGeneratedVoucher(VoucherDomainEvents.VoucherGeneratedEvent domainEvent, + CancellationToken cancellationToken); Task AddMerchant(MerchantDomainEvents.MerchantCreatedEvent domainEvent, CancellationToken cancellationToken); @@ -98,14 +103,14 @@ Task AddMerchantOperator(MerchantDomainEvents.OperatorAssignedToMerchant Task AddMerchantSecurityUser(MerchantDomainEvents.SecurityUserAddedToMerchantEvent domainEvent, CancellationToken cancellationToken); - //Task AddPendingMerchantFeeToSettlement(MerchantFeeAddedPendingSettlementEvent domainEvent, - // CancellationToken cancellationToken); + Task AddPendingMerchantFeeToSettlement(SettlementDomainEvents.MerchantFeeAddedPendingSettlementEvent domainEvent, + CancellationToken cancellationToken); Task AddProductDetailsToTransaction(TransactionDomainEvents.ProductDetailsAddedToTransactionEvent domainEvent, CancellationToken cancellationToken); - //Task AddSettledFeeToStatement(SettledFeeAddedToStatementEvent domainEvent, - // CancellationToken cancellationToken); + Task AddSettledFeeToStatement(SettledFeeAddedToStatementEvent domainEvent, + CancellationToken cancellationToken); Task AddSettledMerchantFeeToSettlement(TransactionDomainEvents.SettledMerchantFeeAddedToTransactionEvent domainEvent, CancellationToken cancellationToken); @@ -113,8 +118,8 @@ Task AddSettledMerchantFeeToSettlement(TransactionDomainEvents.SettledMe Task AddSourceDetailsToTransaction(TransactionDomainEvents.TransactionSourceAddedToTransactionEvent domainEvent, CancellationToken cancellationToken); - //Task AddTransactionToStatement(TransactionAddedToStatementEvent domainEvent, - // CancellationToken cancellationToken); + Task AddTransactionToStatement(TransactionAddedToStatementEvent domainEvent, + CancellationToken cancellationToken); Task CompleteReconciliation(ReconciliationDomainEvents.ReconciliationHasCompletedEvent domainEvent, CancellationToken cancellationToken); @@ -137,8 +142,8 @@ Task CreateReadModel(EstateDomainEvents.EstateCreatedEvent domainEvent, Task CreateSettlement(SettlementDomainEvents.SettlementCreatedForDateEvent domainEvent, CancellationToken cancellationToken); - //Task CreateStatement(StatementCreatedEvent domainEvent, - // CancellationToken cancellationToken); + Task CreateStatement(StatementCreatedEvent domainEvent, + CancellationToken cancellationToken); Task DisableContractProductTransactionFee(ContractDomainEvents.TransactionFeeForProductDisabledEvent domainEvent, CancellationToken cancellationToken); @@ -152,8 +157,8 @@ Task MarkSettlementAsCompleted(SettlementDomainEvents.SettlementComplete Task MarkSettlementAsProcessingStarted(SettlementDomainEvents.SettlementProcessingStartedEvent domainEvent, CancellationToken cancellationToken); - //Task MarkStatementAsGenerated(StatementGeneratedEvent domainEvent, - // CancellationToken cancellationToken); + Task MarkStatementAsGenerated(StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken); Task RecordTransactionAdditionalRequestData(TransactionDomainEvents.AdditionalRequestDataRecordedEvent domainEvent, CancellationToken cancellationToken); @@ -173,23 +178,23 @@ Task StartTransaction(TransactionDomainEvents.TransactionHasStartedEvent Task UpdateEstate(EstateDomainEvents.EstateReferenceAllocatedEvent domainEvent, CancellationToken cancellationToken); - //Task UpdateFileAsComplete(FileProcessingCompletedEvent domainEvent, - // CancellationToken cancellationToken); + Task UpdateFileAsComplete(FileProcessingCompletedEvent domainEvent, + CancellationToken cancellationToken); - //Task UpdateFileLine(FileLineProcessingSuccessfulEvent domainEvent, - // CancellationToken cancellationToken); + Task UpdateFileLine(FileLineProcessingSuccessfulEvent domainEvent, + CancellationToken cancellationToken); - //Task UpdateFileLine(FileLineProcessingFailedEvent domainEvent, - // CancellationToken cancellationToken); + Task UpdateFileLine(FileLineProcessingFailedEvent domainEvent, + CancellationToken cancellationToken); - //Task UpdateFileLine(FileLineProcessingIgnoredEvent domainEvent, - // CancellationToken cancellationToken); + Task UpdateFileLine(FileLineProcessingIgnoredEvent domainEvent, + CancellationToken cancellationToken); Task UpdateMerchant(MerchantDomainEvents.MerchantReferenceAllocatedEvent domainEvent, CancellationToken cancellationToken); - //Task UpdateMerchant(StatementGeneratedEvent domainEvent, - // CancellationToken cancellationToken); + Task UpdateMerchant(StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken); Task UpdateMerchant(MerchantDomainEvents.SettlementScheduleChangedEvent domainEvent, CancellationToken cancellationToken); @@ -291,6 +296,23 @@ public TransactionProcessorReadModelRepository(Shared.EntityFramework.IDbContext } + public async Task UpdateMerchant(StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + Result merchantResult = await context.LoadMerchant(domainEvent, cancellationToken); + if (merchantResult.IsFailed) + return ResultHelpers.CreateFailure(merchantResult); + Merchant? merchant = merchantResult.Data; + + if (merchant.LastStatementGenerated > domainEvent.DateGenerated) + return Result.Success(); + + merchant.LastStatementGenerated = domainEvent.DateGenerated; + return await context.SaveChangesAsync(cancellationToken); + } + public async Task> GetMerchantFromReference(Guid estateId, String reference, CancellationToken cancellationToken) @@ -620,6 +642,31 @@ public async Task UpdateMerchantAddress(MerchantPostalCodeUpdatedEvent d return await context.SaveChangesAsync(cancellationToken); } + public async Task AddGeneratedVoucher(VoucherDomainEvents.VoucherGeneratedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + Voucher voucher = new Voucher + { + ExpiryDateTime = domainEvent.ExpiryDateTime, + ExpiryDate = domainEvent.ExpiryDateTime.Date, + IsGenerated = true, + IsIssued = false, + OperatorIdentifier = domainEvent.OperatorId.ToString(), + Value = domainEvent.Value, + VoucherCode = domainEvent.VoucherCode, + VoucherId = domainEvent.VoucherId, + TransactionId = domainEvent.TransactionId, + GenerateDateTime = domainEvent.GeneratedDateTime, + GenerateDate = domainEvent.GeneratedDateTime.Date + }; + + await context.Vouchers.AddAsync(voucher, cancellationToken); + + return await context.SaveChangesAsync(cancellationToken); + } + public async Task UpdateMerchantContact(MerchantContactNameUpdatedEvent domainEvent, CancellationToken cancellationToken) { EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); @@ -1639,5 +1686,280 @@ public async Task AddContractProduct(ContractDomainEvents.FixedValueProd return await context.SaveChangesAsync(cancellationToken); } + + public async Task MarkStatementAsGenerated(StatementGeneratedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + var getLoadStatementHeaderResult = await context.LoadStatementHeader(domainEvent, cancellationToken); + if (getLoadStatementHeaderResult.IsFailed) + return ResultHelpers.CreateFailure(getLoadStatementHeaderResult); + var statementHeader = getLoadStatementHeaderResult.Data; + + statementHeader.StatementGeneratedDate = domainEvent.DateGenerated; + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task AddSettledFeeToStatement(SettledFeeAddedToStatementEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + // Find the corresponding transaction + var getTransactionResult = await context.LoadTransaction(domainEvent, cancellationToken); + if (getTransactionResult.IsFailed) + return ResultHelpers.CreateFailure(getTransactionResult); + var transaction = getTransactionResult.Data; + + Result operatorResult = await context.LoadOperator(transaction.OperatorId, cancellationToken); + if (operatorResult.IsFailed) + return ResultHelpers.CreateFailure(operatorResult); + var @operator = operatorResult.Data; + + StatementLine line = new StatementLine + { + StatementId = domainEvent.MerchantStatementId, + ActivityDateTime = domainEvent.SettledDateTime, + ActivityDate = domainEvent.SettledDateTime.Date, + ActivityDescription = $"{@operator.Name} Transaction Fee", + ActivityType = 2, // Transaction Fee + TransactionId = domainEvent.TransactionId, + InAmount = domainEvent.SettledValue + }; + + await context.StatementLines.AddAsync(line, cancellationToken); + + return await context.SaveChangesWithDuplicateHandling(cancellationToken); + } + + public async Task CreateStatement(StatementCreatedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + StatementHeader header = new StatementHeader + { + MerchantId = domainEvent.MerchantId, + StatementCreatedDateTime = domainEvent.DateCreated, + StatementCreatedDate = domainEvent.DateCreated.Date, + StatementId = domainEvent.MerchantStatementId + }; + + await context.StatementHeaders.AddAsync(header, cancellationToken); + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task AddTransactionToStatement(TransactionAddedToStatementEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + // Find the corresponding transaction + Result transactionResult = await context.LoadTransaction(domainEvent, cancellationToken); + if (transactionResult.IsFailed) + return ResultHelpers.CreateFailure(transactionResult); + + Transaction transaction = transactionResult.Data; + + Result operatorResult = await context.LoadOperator(transaction.OperatorId, cancellationToken); + if (operatorResult.IsFailed) + return ResultHelpers.CreateFailure(operatorResult); + Operator @operator = operatorResult.Data; + + StatementLine line = new StatementLine + { + StatementId = domainEvent.MerchantStatementId, + ActivityDateTime = domainEvent.TransactionDateTime, + ActivityDate = domainEvent.TransactionDateTime.Date, + ActivityDescription = $"{@operator.Name} Transaction", + ActivityType = 1, // Transaction + TransactionId = domainEvent.TransactionId, + OutAmount = domainEvent.TransactionValue + }; + + await context.StatementLines.AddAsync(line, cancellationToken); + + return await context.SaveChangesWithDuplicateHandling(cancellationToken); + } + + public async Task AddFile(FileCreatedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + File file = new File + { + EstateId = domainEvent.EstateId, + MerchantId = domainEvent.MerchantId, + FileImportLogId = domainEvent.FileImportLogId, + UserId = domainEvent.UserId, + FileId = domainEvent.FileId, + FileProfileId = domainEvent.FileProfileId, + FileLocation = domainEvent.FileLocation, + FileReceivedDateTime = domainEvent.FileReceivedDateTime, + FileReceivedDate = domainEvent.FileReceivedDateTime.Date + }; + + await context.Files.AddAsync(file, cancellationToken); + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task AddFileImportLog(ImportLogCreatedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + FileImportLog fileImportLog = new FileImportLog + { + EstateId = domainEvent.EstateId, + FileImportLogId = domainEvent.FileImportLogId, + ImportLogDateTime = domainEvent.ImportLogDateTime, + ImportLogDate = domainEvent.ImportLogDateTime.Date + }; + + await context.FileImportLogs.AddAsync(fileImportLog, cancellationToken); + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task AddFileLineToFile(FileLineAddedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + FileLine fileLine = new FileLine + { + FileId = domainEvent.FileId, + LineNumber = domainEvent.LineNumber, + FileLineData = domainEvent.FileLine, + Status = "P" // Pending + }; + + await context.FileLines.AddAsync(fileLine, cancellationToken); + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task AddFileToImportLog(FileAddedToImportLogEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + FileImportLogFile fileImportLogFile = new FileImportLogFile + { + MerchantId = domainEvent.MerchantId, + FileImportLogId = domainEvent.FileImportLogId, + FileId = domainEvent.FileId, + FilePath = domainEvent.FilePath, + FileProfileId = domainEvent.FileProfileId, + FileUploadedDateTime = domainEvent.FileUploadedDateTime, + FileUploadedDate = domainEvent.FileUploadedDateTime.Date, + OriginalFileName = domainEvent.OriginalFileName, + UserId = domainEvent.UserId + }; + + await context.FileImportLogFiles.AddAsync(fileImportLogFile, cancellationToken); + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateFileAsComplete(FileProcessingCompletedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + var getFileResult = await context.LoadFile(domainEvent, cancellationToken); + if (getFileResult.IsFailed) + return ResultHelpers.CreateFailure(getFileResult); + + var file = getFileResult.Data; + file.IsCompleted = true; + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateFileLine(FileLineProcessingSuccessfulEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + return await this.UpdateFileLineStatus(context, + domainEvent.FileId, + domainEvent.LineNumber, + domainEvent.TransactionId, + "S", + cancellationToken); + } + + private async Task UpdateFileLineStatus(EstateManagementGenericContext context, + Guid fileId, + Int32 lineNumber, + Guid transactionId, + String newStatus, + CancellationToken cancellationToken) + { + FileLine fileLine = await context.FileLines.SingleOrDefaultAsync(f => f.FileId == fileId && f.LineNumber == lineNumber, cancellationToken: cancellationToken); + + if (fileLine == null) + { + return Result.NotFound($"FileLine number {lineNumber} in File Id {fileId} not found"); + } + + fileLine.Status = newStatus; + fileLine.TransactionId = transactionId; + + return await context.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateFileLine(FileLineProcessingFailedEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + return await this.UpdateFileLineStatus(context, + domainEvent.FileId, + domainEvent.LineNumber, + domainEvent.TransactionId, + "F", + cancellationToken); + } + + public async Task UpdateFileLine(FileLineProcessingIgnoredEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + return await this.UpdateFileLineStatus(context, + domainEvent.FileId, + domainEvent.LineNumber, + Guid.Empty, + "I", + cancellationToken); + } + + public async Task AddPendingMerchantFeeToSettlement(SettlementDomainEvents.MerchantFeeAddedPendingSettlementEvent domainEvent, + CancellationToken cancellationToken) + { + EstateManagementGenericContext context = await this.GetContextFromDomainEvent(domainEvent, cancellationToken); + + MerchantSettlementFee merchantSettlementFee = new MerchantSettlementFee + { + SettlementId = domainEvent.SettlementId, + CalculatedValue = domainEvent.CalculatedValue, + FeeCalculatedDateTime = domainEvent.FeeCalculatedDateTime, + ContractProductTransactionFeeId = domainEvent.FeeId, + FeeValue = domainEvent.FeeValue, + IsSettled = false, + MerchantId = domainEvent.MerchantId, + TransactionId = domainEvent.TransactionId + }; + + await context.MerchantSettlementFees.AddAsync(merchantSettlementFee, cancellationToken); + + return await context.SaveChangesWithDuplicateHandling(cancellationToken); + } } } diff --git a/TransactionProcessor.Repository/TransactionProcessor.Repository.csproj b/TransactionProcessor.Repository/TransactionProcessor.Repository.csproj index eda23eee..82086da1 100644 --- a/TransactionProcessor.Repository/TransactionProcessor.Repository.csproj +++ b/TransactionProcessor.Repository/TransactionProcessor.Repository.csproj @@ -8,6 +8,8 @@ + + diff --git a/TransactionProcessor.Testing/TestData.cs b/TransactionProcessor.Testing/TestData.cs index b4abdc84..91bb4104 100644 --- a/TransactionProcessor.Testing/TestData.cs +++ b/TransactionProcessor.Testing/TestData.cs @@ -1,5 +1,6 @@ using CallbackHandler.DataTransferObjects; using Newtonsoft.Json; +using Shared.ValueObjects; using TransactionProcessor.Aggregates; using TransactionProcessor.BusinessLogic.Events; using TransactionProcessor.DataTransferObjects.Requests.Contract; @@ -2076,9 +2077,25 @@ public static DataTransferObjects.Requests.Contract.AddTransactionFeeForProductT }; public static Guid EventId = Guid.Parse("0F537961-A19C-4A29-80DC-68889474142B"); + + public static Boolean IsAuthorisedFalse = false; + + public static Boolean IsAuthorisedTrue = true; + + public static GenerateMerchantStatementRequest GenerateMerchantStatementRequest => + new GenerateMerchantStatementRequest + { + MerchantStatementDate = TestData.StatementCreateDate + }; #endregion public static class Commands { + public static MerchantCommands.GenerateMerchantStatementCommand GenerateMerchantStatementCommand => new(TestData.EstateId, TestData.MerchantId, TestData.GenerateMerchantStatementRequest); + + public static MerchantStatementCommands.AddTransactionToMerchantStatementCommand AddTransactionToMerchantStatementCommand => new(EstateId, MerchantId, TransactionDateTime, TransactionAmount, IsAuthorisedTrue, TransactionId); + public static MerchantStatementCommands.EmailMerchantStatementCommand EmailMerchantStatementCommand => new(EstateId, MerchantId, MerchantStatementId); + public static MerchantStatementCommands.AddSettledFeeToMerchantStatementCommand AddSettledFeeToMerchantStatementCommand => new(EstateId, MerchantId, TransactionDateTime, SettledFeeAmount1, TransactionId, SettledFeeId1); + public static TransactionCommands.ResendTransactionReceiptCommand ResendTransactionReceiptCommand => new(TestData.TransactionId, TestData.EstateId); @@ -2554,6 +2571,9 @@ public static Models.Merchant.Merchant MerchantModelWithAddressesContactsDevices }; public static class DomainEvents { + public static MerchantStatementDomainEvents.StatementCreatedEvent StatementCreatedEvent => new MerchantStatementDomainEvents.StatementCreatedEvent(TestData.MerchantStatementId, TestData.EstateId, TestData.MerchantId, TestData.StatementCreateDate); + + public static MerchantStatementDomainEvents.StatementGeneratedEvent StatementGeneratedEvent => new MerchantStatementDomainEvents.StatementGeneratedEvent(TestData.MerchantStatementId, TestData.EstateId, TestData.MerchantId, TestData.StatementGeneratedDate); public static CallbackReceivedEnrichedEvent CallbackReceivedEnrichedEventDeposit => new CallbackReceivedEnrichedEvent(TestData.CallbackId) { diff --git a/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs b/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs index 16809cb5..d3f26d60 100644 --- a/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs +++ b/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs @@ -73,10 +73,9 @@ public DomainEventHandlerRegistry() }); this.AddSingleton(); + this.AddSingleton(); this.AddSingleton(); this.AddSingleton(); - this.AddSingleton(); - this.AddSingleton(); this.AddSingleton(); this.AddSingleton>(); this.AddSingleton>(); diff --git a/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs b/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs index 36fc1af4..b7266e4a 100644 --- a/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs +++ b/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs @@ -28,6 +28,7 @@ public DomainServiceRegistry() this.AddSingleton(); this.AddSingleton(); this.AddSingleton(); + this.AddSingleton(); } #endregion diff --git a/TransactionProcessor/Bootstrapper/MediatorRegistry.cs b/TransactionProcessor/Bootstrapper/MediatorRegistry.cs index 96939600..32ff0eab 100644 --- a/TransactionProcessor/Bootstrapper/MediatorRegistry.cs +++ b/TransactionProcessor/Bootstrapper/MediatorRegistry.cs @@ -41,6 +41,14 @@ public MediatorRegistry() this.RegisterEstateRequestHandler(); this.RegisterOperatorRequestHandler(); this.RegisterContractRequestHandler(); + this.RegisterMerchantStatementRequestHandler(); + } + + private void RegisterMerchantStatementRequestHandler() { + this.AddSingleton, MerchantStatementRequestHandler>(); + this.AddSingleton, MerchantStatementRequestHandler>(); + this.AddSingleton, MerchantStatementRequestHandler>(); + this.AddSingleton, MerchantStatementRequestHandler>(); } #endregion diff --git a/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs b/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs index 2a781b06..120cf9de 100644 --- a/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs +++ b/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs @@ -47,7 +47,8 @@ public MiddlewareRegistry() tags:new[] {"db", "eventstore"}).AddSecurityService(this.ApiEndpointHttpHandler).AddEstateManagementService(); this.AddSwaggerGen(c => - { + { + c.CustomSchemaIds(type => type.FullName); // Uses the full namespace as schema ID c.SwaggerDoc("v1", new OpenApiInfo { diff --git a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs index fffed42b..fb9e8922 100644 --- a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs +++ b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs @@ -79,6 +79,7 @@ public RepositoryRegistry() this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); this.AddSingleton, AggregateRepository>(); + this.AddSingleton, AggregateRepository>(); this.AddSingleton, MerchantBalanceStateRepository>(); this.AddSingleton, VoucherStateRepository>(); diff --git a/TransactionProcessor/Controllers/EstateController.cs b/TransactionProcessor/Controllers/EstateController.cs index 0de14539..ca674d3e 100644 --- a/TransactionProcessor/Controllers/EstateController.cs +++ b/TransactionProcessor/Controllers/EstateController.cs @@ -5,8 +5,6 @@ using Shared.Exceptions; using Shared.General; using SimpleResults; -using Swashbuckle.AspNetCore.Annotations; -using Swashbuckle.AspNetCore.Filters; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Security.Claims; diff --git a/TransactionProcessor/Controllers/MerchantController.cs b/TransactionProcessor/Controllers/MerchantController.cs index 76a59adc..2deafa81 100644 --- a/TransactionProcessor/Controllers/MerchantController.cs +++ b/TransactionProcessor/Controllers/MerchantController.cs @@ -786,4 +786,26 @@ public async Task UpdateMerchantContact([FromRoute] Guid estateId // return the result return result.ToActionResultX(); } + + [HttpPost] + [Route("{merchantId}/statements")] + public async Task GenerateMerchantStatement([FromRoute] Guid estateId, + [FromRoute] Guid merchantId, + [FromBody] GenerateMerchantStatementRequest generateMerchantStatementRequest, + CancellationToken cancellationToken) + { + bool isRequestAllowed = PerformStandardChecks(estateId); + if (isRequestAllowed == false) + { + return Forbid(); + } + + MerchantCommands.GenerateMerchantStatementCommand command = new(estateId, merchantId, generateMerchantStatementRequest); + + // Route the command + Result result = await Mediator.Send(command, cancellationToken); + + // return the result + return result.ToActionResultX(); + } } \ No newline at end of file diff --git a/TransactionProcessor/Controllers/SettlementController.cs b/TransactionProcessor/Controllers/SettlementController.cs index 4f5e8339..74316691 100644 --- a/TransactionProcessor/Controllers/SettlementController.cs +++ b/TransactionProcessor/Controllers/SettlementController.cs @@ -2,6 +2,7 @@ using EstateManagement.DataTransferObjects.Responses.Settlement; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; using SecurityService.Client; using SecurityService.DataTransferObjects.Responses; using Shared.Results; @@ -30,7 +31,7 @@ namespace TransactionProcessor.Controllers [ExcludeFromCodeCoverage] [Route(SettlementController.ControllerRoute)] [ApiController] - [Authorize] + //[Authorize] public class SettlementController : ControllerBase { private readonly IAggregateRepository SettlmentAggregateRepository; @@ -49,7 +50,7 @@ public class SettlementController : ControllerBase /// /// The controller route /// - private const String ControllerRoute = "api/" + SettlementController.ControllerName; + private const String ControllerRoute = "api/estates/{estateId}/" + SettlementController.ControllerName; public SettlementController(IAggregateRepository settlementAggregateRepository, IMediator mediator, @@ -63,7 +64,7 @@ public SettlementController(IAggregateRepository GetPendingSettlement([FromRoute] DateTime settlementDate, [FromRoute] Guid estateId, [FromRoute] Guid merchantId, @@ -101,7 +102,7 @@ public async Task GetPendingSettlement([FromRoute] DateTime settl } [HttpPost] - [Route("{settlementDate}/estates/{estateId}/merchants/{merchantId}")] + [Route("{settlementDate}/merchants/{merchantId}")] public async Task ProcessSettlement([FromRoute] DateTime settlementDate, [FromRoute] Guid estateId, [FromRoute] Guid merchantId, diff --git a/TransactionProcessor/Controllers/TransactionController.cs b/TransactionProcessor/Controllers/TransactionController.cs index 70c98998..fd082447 100644 --- a/TransactionProcessor/Controllers/TransactionController.cs +++ b/TransactionProcessor/Controllers/TransactionController.cs @@ -100,7 +100,8 @@ public async Task PerformTransaction([FromBody] SerialisedMessage } [HttpPost] - [Route("/api/{estateId}/transactions/{transactionId}/resendreceipt")][SwaggerResponseExample(201, typeof(TransactionResponseExample))] + [Route("/api/{estateId}/transactions/{transactionId}/resendreceipt")] + [SwaggerResponseExample(201, typeof(TransactionResponseExample))] public async Task ResendTransactionReceipt([FromRoute] Guid estateId, [FromRoute] Guid transactionId, CancellationToken cancellationToken) diff --git a/TransactionProcessor/Controllers/VoucherController.cs b/TransactionProcessor/Controllers/VoucherController.cs index 595cfa4a..35b7e350 100644 --- a/TransactionProcessor/Controllers/VoucherController.cs +++ b/TransactionProcessor/Controllers/VoucherController.cs @@ -1,6 +1,7 @@ using Shared.EventStore.Aggregate; using Shared.Results; using SimpleResults; +using Swashbuckle.AspNetCore.Filters; using TransactionProcessor.BusinessLogic.Requests; namespace TransactionProcessor.Controllers @@ -10,7 +11,6 @@ namespace TransactionProcessor.Controllers using System.Threading; using System.Threading.Tasks; using BusinessLogic.Manager; - using Common.Examples; using DataTransferObjects; using Factories; using MediatR; @@ -19,7 +19,7 @@ namespace TransactionProcessor.Controllers using Models; using Shared.General; using Swashbuckle.AspNetCore.Annotations; - using Swashbuckle.AspNetCore.Filters; + using TransactionProcessor.Common.Examples; using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; using IssueVoucherResponse = Models.IssueVoucherResponse; using RedeemVoucherResponse = Models.RedeemVoucherResponse; @@ -74,7 +74,7 @@ public async Task RedeemVoucher(RedeemVoucherRequest redeemVouche Result result = await this.Mediator.Send(command, cancellationToken); if (result.IsFailed) - ResultHelpers.CreateFailure(result).ToActionResultX(); + return ResultHelpers.CreateFailure(result).ToActionResultX(); return ModelFactory.ConvertFrom(result.Data).ToActionResultX(); } @@ -103,7 +103,7 @@ public async Task GetVoucher([FromQuery] Guid estateId, // By code is priority Result getVoucherByCodeResult = await this.Mediator.Send(queryByVoucherCode, cancellationToken); if (getVoucherByCodeResult.IsFailed) - ResultHelpers.CreateFailure(getVoucherByCodeResult).ToActionResultX(); + return ResultHelpers.CreateFailure(getVoucherByCodeResult).ToActionResultX(); return ModelFactory.ConvertFrom(getVoucherByCodeResult.Data).ToActionResultX(); } @@ -113,7 +113,7 @@ public async Task GetVoucher([FromQuery] Guid estateId, VoucherQueries.GetVoucherByTransactionIdQuery queryByTransactionId = new(estateId, transactionId); Result getVoucherByTransactionIdResult = await this.Mediator.Send(queryByTransactionId, cancellationToken); if (getVoucherByTransactionIdResult.IsFailed) - ResultHelpers.CreateFailure(getVoucherByTransactionIdResult).ToActionResultX(); + return ResultHelpers.CreateFailure(getVoucherByTransactionIdResult).ToActionResultX(); return ModelFactory.ConvertFrom(getVoucherByTransactionIdResult.Data).ToActionResultX(); } diff --git a/TransactionProcessor/TransactionProcessor.csproj b/TransactionProcessor/TransactionProcessor.csproj index 230a77f1..39370b44 100644 --- a/TransactionProcessor/TransactionProcessor.csproj +++ b/TransactionProcessor/TransactionProcessor.csproj @@ -39,13 +39,13 @@ - - - - - - - + + + + + + + diff --git a/TransactionProcessor/appsettings.json b/TransactionProcessor/appsettings.json index 1ef8f2b8..23bb935c 100644 --- a/TransactionProcessor/appsettings.json +++ b/TransactionProcessor/appsettings.json @@ -7,146 +7,239 @@ //"SecurityService": "https://127.0.0.1:5001", "ProjectionTraceThresholdInSeconds": 1, "EventHandlerConfiguration": { - // Transaction Event Handler - "TransactionHasBeenCompletedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" - ], - "CustomerEmailReceiptRequestedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" - ], - "SettledMerchantFeeAddedToTransactionEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" - ], - "MerchantFeeSettledEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" - ], - "MerchantFeePendingSettlementAddedToTransactionEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" - ], - "VoucherIssuedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.VoucherDomainEventHandler, TransactionProcessor.BusinessLogic" - ], - "TransactionCostInformationRecordedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler, TransactionProcessor.BusinessLogic" - ], - "FloatCreditPurchasedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler, TransactionProcessor.BusinessLogic" - ], + // Read Model Events // Estate Domain Event Handler "EstateCreatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.EstateDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "SecurityUserAddedToEstateEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.EstateDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "EstateReferenceAllocatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.EstateDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], // Merchant Domain Event Handler "MerchantCreatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "MerchantReferenceAllocatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "AddressAddedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "ContactAddedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "SecurityUserAddedToMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "DeviceAddedToMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "DeviceSwappedForMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "OperatorAssignedToMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "OperatorRemovedFromMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "SettlementScheduleChangedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "CallbackReceivedEnrichedEvent": [ "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" ], "ContractAddedToMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "ContractRemovedFromMerchantEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantNameUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantAddressLine1UpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantAddressLine2UpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantAddressLine3UpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantAddressLine4UpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantCountyUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantRegionUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantTownUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantPostalCodeUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantContactNameUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantContactEmailAddressUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], "MerchantContactPhoneNumberUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler,TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + + // Contract Domain Event Handler + "ContractCreatedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "FixedValueProductAddedToContractEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "VariableValueProductAddedToContractEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "TransactionFeeForProductAddedToContractEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "TransactionFeeForProductDisabledEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + + // Transaction Event Handler + "TransactionHasBeenCompletedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "CustomerEmailReceiptRequestedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "SettledMerchantFeeAddedToTransactionEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "MerchantFeeSettledEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "MerchantFeePendingSettlementAddedToTransactionEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "TransactionCostInformationRecordedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler, TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "FloatCreditPurchasedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler, TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "FloatCreatedForContractProductEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "FloatDecreasedByTransactionEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "TransactionHasStartedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "AdditionalRequestDataRecordedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "AdditionalResponseDataRecordedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "TransactionHasBeenLocallyAuthorisedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "TransactionHasBeenLocallyDeclinedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "TransactionAuthorisedByOperatorEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "TransactionDeclinedByOperatorEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "ProductDetailsAddedToTransactionEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "TransactionSourceAddedToTransactionEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "ReconciliationHasStartedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "OverallTotalsRecordedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "ReconciliationHasBeenLocallyAuthorisedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "ReconciliationHasBeenLocallyDeclinedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "ReconciliationHasCompletedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "VoucherGeneratedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "VoucherIssuedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.VoucherDomainEventHandler, TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "VoucherFullyRedeemedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler,TransactionProcessor.BusinessLogic" ], // Operator Domain Event Handler "OperatorCreatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.OperatorDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "OperatorNameUpdatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.OperatorDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "OperatorRequireCustomMerchantNumberChangedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.OperatorDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], "OperatorRequireCustomTerminalNumberChangedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.OperatorDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], - // Contract Domain Event Handler - "ContractCreatedEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.ContractDomainEventHandler, TransactionProcessor.BusinessLogic" + + // Merchant Statement Domain Event Handler + "StatementCreatedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], - "FixedValueProductAddedToContractEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.ContractDomainEventHandler, TransactionProcessor.BusinessLogic" + "TransactionAddedToStatementEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], - "VariableValueProductAddedToContractEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.ContractDomainEventHandler, TransactionProcessor.BusinessLogic" + "SettledFeeAddedToStatementEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ], - "TransactionFeeForProductAddedToContractEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.ContractDomainEventHandler, TransactionProcessor.BusinessLogic" + "StatementGeneratedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic", + "TransactionProcessor.BusinessLogic.EventHandling.MerchantDomainEventHandler, TransactionProcessor.BusinessLogic" ], - "TransactionFeeForProductDisabledEvent": [ - "TransactionProcessor.BusinessLogic.EventHandling.ContractDomainEventHandler, TransactionProcessor.BusinessLogic" + // Settlement Domain Event Handler + "SettlementCreatedForDateEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "MerchantFeeAddedPendingSettlementEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "SettlementCompletedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" + ], + "SettlementProcessingStartedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.ReadModelDomainEventHandler, TransactionProcessor.BusinessLogic" ] + + }, "EventHandlerConfigurationOrdered": { "EstateCreatedEvent": [ @@ -168,7 +261,8 @@ "TransactionProcessor.ProjectionEngine.EventHandling.EventHandler,TransactionProcessor.ProjectionEngine" ], "TransactionHasBeenCompletedEvent": [ - "TransactionProcessor.ProjectionEngine.EventHandling.EventHandler,TransactionProcessor.ProjectionEngine" + "TransactionProcessor.ProjectionEngine.EventHandling.EventHandler,TransactionProcessor.ProjectionEngine", + "TransactionProcessor.BusinessLogic.EventHandling.MerchantStatementDomainEventHandler,TransactionProcessor.BusinessLogic" ], "CustomerEmailReceiptRequestedEvent": [ "TransactionProcessor.BusinessLogic.EventHandling.TransactionDomainEventHandler,TransactionProcessor.BusinessLogic" @@ -187,6 +281,12 @@ ], "VoucherFullyRedeeemedEvent": [ "TransactionProcessor.ProjectionEngine.EventHandling.EventHandler,TransactionProcessor.ProjectionEngine" + ], + "MerchantFeeSettledEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.MerchantSettlementDomainEventHandler,TransactionProcessor.BusinessLogic" + ], + "StatementGeneratedEvent": [ + "TransactionProcessor.BusinessLogic.EventHandling.MerchantStatementDomainEventHandler,TransactionProcessor.BusinessLogic" ] }, "EventStateConfig": { From 96cc6db85afd4647a4483d306145d4eb620358e2 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Thu, 13 Feb 2025 13:24:25 +0000 Subject: [PATCH 3/3] remove redundant test --- .../OperatorDomainEventHandlerTests.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs index 5297825f..aadf153a 100644 --- a/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/DomainEventHandlers/OperatorDomainEventHandlerTests.cs @@ -53,12 +53,4 @@ public void OperatorDomainEventHandler_OperatorRequireCustomTerminalNumberChange Should.NotThrow(async () => { await this.DomainEventHandler.Handle(operatorCreatedEvent, CancellationToken.None); }); } - - [Fact] - public void OperatorDomainEventHandler_EstateCreatedEvent_EventIsHandled() - { - EstateDomainEvents.EstateCreatedEvent domainEvent = TestData.DomainEvents.EstateCreatedEvent; - - Should.NotThrow(async () => { await this.DomainEventHandler.Handle(domainEvent, CancellationToken.None); }); - } } \ No newline at end of file