From edb10b92d8d66153b2ce2bf832114d3bd237aef3 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Wed, 3 Sep 2025 10:38:49 +0100 Subject: [PATCH 1/2] Add timeDelay parameter to SendSales method Updated the SendSales method in ITransactionDataGeneratorService to include a timeDelay parameter for processing sales. The TransactionDataGeneratorService class now incorporates this delay, allowing for random delays between sales transactions, with the number of sales adjusted to a range of 1 to 5. All relevant method calls have been updated to utilize the new parameter. Additionally, the BaseConfiguration class has a new nullable TaskDelay property, and the Jobs class has been modified to respect the configured delay. These changes enhance the flexibility and timing of the sales generation process. --- .../DataGenerator/Program.cs | 3 +- .../ITransactionDataGeneratorService.cs | 2 +- .../TransactionDataGeneratorService.cs | 97 ++++++++----------- .../Jobs/Jobs.cs | 6 +- .../Configuration/BaseConfiguration.cs | 27 ++---- .../Jobs/Jobs.cs | 6 +- 6 files changed, 60 insertions(+), 81 deletions(-) diff --git a/TransactionProcessing.SchedulerService/DataGenerator/Program.cs b/TransactionProcessing.SchedulerService/DataGenerator/Program.cs index d52797a..6465039 100644 --- a/TransactionProcessing.SchedulerService/DataGenerator/Program.cs +++ b/TransactionProcessing.SchedulerService/DataGenerator/Program.cs @@ -193,9 +193,8 @@ await g.MakeFloatDeposit(dateTime, estateId, contractResponse.ContractId, foreach (ContractResponse contract in getMerchantContractsResult.Data) { // Generate and send some sales - await g.SendSales(dateTime, merchant, contract, 0, cancellationToken); + await g.SendSales(dateTime, merchant, contract, 0,0, cancellationToken); } - } } diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/ITransactionDataGeneratorService.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/ITransactionDataGeneratorService.cs index 00cf3f5..f987d46 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/ITransactionDataGeneratorService.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/ITransactionDataGeneratorService.cs @@ -22,7 +22,7 @@ Task PerformMerchantSettlement(DateTime dateTime, Guid estateId, Guid merchantId, CancellationToken cancellationToken); - Task SendSales(DateTime dateTime, MerchantResponse merchant, ContractResponse contract, Int32 numberOfSales, CancellationToken cancellationToken); + Task SendSales(DateTime dateTime, MerchantResponse merchant, ContractResponse contract, Int32 numberOfSales, Int32 timeDelay, CancellationToken cancellationToken); Task SendUploadFile(DateTime dateTime, ContractResponse contract, MerchantResponse merchant, Guid userId, CancellationToken cancellationToken); Task> GetMerchant(Guid estateId, Guid merchantId, CancellationToken cancellationToken); Task GenerateMerchantStatement(Guid estateId, Guid merchantId, DateTime statementDateTime, CancellationToken cancellationToken); diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs index 64ee948..e399386 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs @@ -1,11 +1,12 @@ -using System.Net.Http.Headers; -using System.Text; -using Newtonsoft.Json; +using Newtonsoft.Json; using Polly; using SecurityService.Client; using SecurityService.DataTransferObjects.Responses; using Shared.Results; using SimpleResults; +using System; +using System.Net.Http.Headers; +using System.Text; using TransactionProcessor.Client; using TransactionProcessor.DataTransferObjects; using TransactionProcessor.DataTransferObjects.Requests.Merchant; @@ -201,62 +202,55 @@ public async Task SendSales(DateTime dateTime, MerchantResponse merchant, ContractResponse contract, Int32 numberOfSales, + Int32 timeDelay, CancellationToken cancellationToken) { List salesToSend = new List(); Decimal depositAmount = 0; (Int32 accountNumber, String accountName, Decimal balance) billDetails = default; (Int32 meterNumber, String customerName, Decimal amount) meterDetails = default; - foreach (ContractProduct contractProduct in contract.Products) - { - this.WriteTrace($"product [{contractProduct.DisplayText}]"); - - List<(SaleTransactionRequest request, Decimal amount)> saleRequests = null; - // Get a number of sales to be sent - if (numberOfSales == 0) - { - numberOfSales = this.r.Next(2, 10); - } + // Step 2: Decide how many total transactions to generate in this run + numberOfSales = this.r.Next(1, 6); // e.g. 1–5 transactions per run + List<(SaleTransactionRequest request, Decimal amount)> saleRequests = null; + for (int i = 0; i < numberOfSales; i++) { + ContractProduct? contractProduct = contract.Products[this.r.Next(contract.Products.Count)]; - for (Int32 i = 1; i <= numberOfSales; i++) - { - ProductSubType productSubType = this.GetProductSubType(contract.OperatorName); + // Spread transactions randomly across 5 minutes + int delayMs = this.r.Next(0, timeDelay * 60 * 1000); - if (productSubType == ProductSubType.BillPaymentPostPay) - { - // Create a bill for this sale - billDetails = await this.CreateBillPaymentBill(contract.OperatorName, contractProduct, cancellationToken); - } + await Task.Delay(delayMs); + ProductSubType productSubType = this.GetProductSubType(contract.OperatorName); - if (productSubType == ProductSubType.BillPaymentPrePay) - { - // Create a meter - meterDetails = await this.CreateBillPaymentMeter(contract.OperatorName, contractProduct, cancellationToken); - } + if (productSubType == ProductSubType.BillPaymentPostPay) { + // Create a bill for this sale + billDetails = await this.CreateBillPaymentBill(contract.OperatorName, contractProduct, cancellationToken); + } - saleRequests = productSubType switch - { - ProductSubType.MobileTopup => this.BuildMobileTopupSaleRequests(dateTime, merchant, contract, contractProduct), - ProductSubType.Voucher => this.BuildVoucherSaleRequests(dateTime, merchant, contract, contractProduct), - ProductSubType.BillPaymentPostPay => this.BuildPataPawaPostPayBillPaymentSaleRequests(dateTime, merchant, contract, contractProduct, billDetails), - ProductSubType.BillPaymentPrePay => this.BuildPataPawaPrePayBillPaymentSaleRequests(dateTime, merchant, contract, contractProduct, meterDetails), - _ => throw new Exception($"Product Sub Type [{productSubType}] not yet supported") - }; + if (productSubType == ProductSubType.BillPaymentPrePay) { + // Create a meter + meterDetails = await this.CreateBillPaymentMeter(contract.OperatorName, contractProduct, cancellationToken); + } - // Add the value of the sale to the deposit amount - Boolean addToDeposit = i switch - { - _ when i == numberOfSales => false, - _ => true - }; + saleRequests = productSubType switch { + ProductSubType.MobileTopup => this.BuildMobileTopupSaleRequests(dateTime, merchant, contract, contractProduct), + ProductSubType.Voucher => this.BuildVoucherSaleRequests(dateTime, merchant, contract, contractProduct), + ProductSubType.BillPaymentPostPay => this.BuildPataPawaPostPayBillPaymentSaleRequests(dateTime, merchant, contract, contractProduct, billDetails), + ProductSubType.BillPaymentPrePay => this.BuildPataPawaPrePayBillPaymentSaleRequests(dateTime, merchant, contract, contractProduct, meterDetails), + _ => throw new Exception($"Product Sub Type [{productSubType}] not yet supported") + }; - if (addToDeposit) - { - depositAmount += saleRequests.Sum(sr => sr.amount); - } + // Add the value of the sale to the deposit amount + Boolean addToDeposit = i switch { + _ when i == numberOfSales => false, + _ => true + }; - salesToSend.AddRange(saleRequests.Select(s => s.request)); + if (addToDeposit) { + depositAmount += saleRequests.Sum(sr => sr.amount); } + + salesToSend.AddRange(saleRequests.Select(s => s.request)); + } // Build up a deposit (minus the last sale amount) @@ -274,24 +268,19 @@ public async Task SendSales(DateTime dateTime, Int32 salesSent = 0; IOrderedEnumerable orderedSales = salesToSend.OrderBy(s => s.TransactionDateTime); // Send the sales to the host - foreach (SaleTransactionRequest sale in orderedSales) - { + foreach (SaleTransactionRequest sale in orderedSales) { sale.TransactionNumber = this.GetTransactionNumber().ToString(); Result saleResult = await this.SendSaleTransaction(merchant, sale, cancellationToken); - if (saleResult.IsSuccess) - { + if (saleResult.IsSuccess) { salesSent++; } - //var random = new Random(); - //int delaySeconds = random.Next(5, 10); // 30–60 inclusive - //await Task.Delay(TimeSpan.FromSeconds(delaySeconds), cancellationToken); } - if (salesSent == 0) - { + if (salesSent == 0) { // All sales failed return Result.Failure("All sales have failed"); } + this.WriteTraceX($"{salesSent} sales for merchant {merchant.MerchantName} sent to host {orderedSales.Count() - salesSent} sales failed to send"); return Result.Success(); } diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/Jobs/Jobs.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/Jobs/Jobs.cs index e4aa1e8..5b3bc19 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/Jobs/Jobs.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.Jobs/Jobs/Jobs.cs @@ -164,14 +164,14 @@ public static async Task GenerateTransactions(ITransactionDataGeneratorS } Random r = new Random(); - List results = new List(); + List results = new(); foreach (ContractResponse contract in contracts) { if (config.ContractNames.Contains(contract.Description) == false) continue; int numberOfSales = r.Next(2, 4); // Generate and send some sales - Result saleResult = await t.SendSales(transactionDate, merchant, contract, numberOfSales, cancellationToken); + Result saleResult = await t.SendSales(transactionDate, merchant, contract, numberOfSales, 0,cancellationToken); if (saleResult.IsFailed) { results.Add(contract.OperatorName); @@ -179,7 +179,7 @@ public static async Task GenerateTransactions(ITransactionDataGeneratorS } if (results.Any()) { - return Result.Failure($"Error sending sales files for merchant [{merchant.MerchantName}] [{string.Join(",", results)}]"); + return Result.Failure($"Error sending sales files for merchant [{merchant.MerchantName}] [{String.Join(",", results)}]"); } return Result.Success(); diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Configuration/BaseConfiguration.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Configuration/BaseConfiguration.cs index 584395d..3e7792e 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Configuration/BaseConfiguration.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Configuration/BaseConfiguration.cs @@ -10,50 +10,41 @@ public class BaseConfiguration { public Boolean IsEnabled { get; set; } = true; + public Int32? TaskDelay { get; set; } } - public class ReplayParkedQueueJobConfiguration : BaseConfiguration - { - - } - - public class MakeFloatCreditsJobConfiguration : BaseConfiguration - { + public class ReplayParkedQueueJobConfiguration : BaseConfiguration; + public class MakeFloatCreditsJobConfiguration : BaseConfiguration { public Guid EstateId { get; set; } public List DepositAmounts { get; set; } = new List(); } - public class DepositAmount : BaseConfiguration - { + public class DepositAmount : BaseConfiguration { public Guid ContractId { get; set; } public Guid ProductId { get; set; } public Decimal Amount { get; set; } } - public class UploadTransactionFileJobConfiguration : BaseConfiguration -{ + public class UploadTransactionFileJobConfiguration : BaseConfiguration { public Guid EstateId { get; set; } public Guid MerchantId { get; set; } public Guid UserId { get; set; } public List ContractsToInclude { get; set; } -} + } - public class GenerateTransactionsJobConfiguration : BaseConfiguration -{ + public class GenerateTransactionsJobConfiguration : BaseConfiguration { public Guid EstateId { get; set; } public Guid MerchantId { get; set; } } - public class ProcessSettlementJobConfiguration : BaseConfiguration -{ + public class ProcessSettlementJobConfiguration : BaseConfiguration { public Guid EstateId { get; set; } public Guid MerchantId { get; set; } } - public class MerchantStatementJobConfiguration : BaseConfiguration -{ + public class MerchantStatementJobConfiguration : BaseConfiguration { public Guid EstateId { get; set; } public Guid MerchantId { get; set; } } diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs index c03d67b..6554a44 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs @@ -107,7 +107,6 @@ public async Task ReplayParkedQueue(TickerFunctionContext ReplayParkedQueues(String eventStoreAddress, public static async Task GenerateSaleTransactions(ITransactionDataGeneratorService t, Guid estateId, Guid merchantId, + Int32 timeDelay, CancellationToken cancellationToken) { // get the merchant @@ -400,7 +400,7 @@ public static async Task GenerateSaleTransactions(ITransactionDataGenera foreach (ContractResponse contract in contracts) { int numberOfSales = r.Next(1, 2); // Generate and send some sales - Result saleResult = await t.SendSales(transactionDate, merchant, contract, numberOfSales, cancellationToken); + Result saleResult = await t.SendSales(transactionDate, merchant, contract, numberOfSales, timeDelay, cancellationToken); if (saleResult.IsFailed) { From b8ee3563a6ed11fd920cca9182ae8e775f5df6b7 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Wed, 3 Sep 2025 10:40:38 +0100 Subject: [PATCH 2/2] :| --- .../TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs index 6554a44..9e614aa 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ/Jobs/Jobs.cs @@ -398,7 +398,7 @@ public static async Task GenerateSaleTransactions(ITransactionDataGenera Random r = new Random(); List results = new List(); foreach (ContractResponse contract in contracts) { - int numberOfSales = r.Next(1, 2); + int numberOfSales = r.Next(1, 5); // Generate and send some sales Result saleResult = await t.SendSales(transactionDate, merchant, contract, numberOfSales, timeDelay, cancellationToken);