diff --git a/Scripts/Sales Reconciliation.js b/Scripts/Sales Reconciliation.js new file mode 100644 index 0000000..b7bbcaa --- /dev/null +++ b/Scripts/Sales Reconciliation.js @@ -0,0 +1,99 @@ + +select CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')), +count(*) as 'count', +SUM(case [transaction].TransactionType + WHEN 'Logon' THEN 1 + ELSE 0 END) as logoncount, +SUM(case [transaction].TransactionType + WHEN 'Sale' THEN 1 + ELSE 0 END) as salecount, +SUM(case [transaction].IsCompleted + WHEN 1 THEN 1 + ELSE 0 END) as completedcount, +SUM(case [transaction].IsAuthorised + WHEN 1 THEN 1 + ELSE 0 END) as authorisedcount, +SUM(case [transaction].IsAuthorised + WHEN 0 THEN 1 + ELSE 0 END) as failedcount, +SUM([transaction].TransactionAmount) as totalamount +from [transaction] +group by CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')) +order by CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')) asc + + +fromAll() + .when({ + $init: function () { + return { + monthlySales: {} + }; + }, + + TransactionHasStartedEvent: function (state, event) { + const data = event.body; + + // Check transactionType + if (data.transactionType !== "Sale" && data.transactionType !== "Logon") { + return state; + } + + const monthKey = getMonthKey(data.transactionDateTime); + const monthly = ensureMonthEntry(state, monthKey); + + monthly.count += 1; + if (data.transactionType === "Sale") { + monthly.saleCount += 1; + if (typeof data.transactionAmount !== "number") { + monthly.totalAmount += 0; + return state; + } + monthly.totalAmount += data.transactionAmount; + } + + if (data.transactionType === "Logon") { + monthly.logonCount += 1; + } + + return state; + }, + + TransactionHasBeenCompletedEvent: function (state, event) { + const data = event.body; + const monthKey = getMonthKey(data.transactionDateTime); + const monthly = ensureMonthEntry(state, monthKey); + + monthly.completedCount += 1; + + if (data.isAuthorised === true) { + monthly.authorisedcount += 1; + } else { + monthly.failedCount += 1; + } + + return state; + } + }); + +function getMonthKey(dateString) { + const date = new Date(dateString); + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; +} + +function ensureMonthEntry(state, monthKey) { + if (!state.monthlySales.hasOwnProperty(monthKey)) { + state.monthlySales[monthKey] = { + count: 0, + logonCount: 0, + saleCount: 0, + completedCount: 0, + authorisedcount: 0, + failedCount: 0, + totalAmount: 0, + + + }; + } + + return state.monthlySales[monthKey]; +} diff --git a/TransactionProcessing.SchedulerService/DataGenerator/Program.cs b/TransactionProcessing.SchedulerService/DataGenerator/Program.cs index 5481121..31aa99f 100644 --- a/TransactionProcessing.SchedulerService/DataGenerator/Program.cs +++ b/TransactionProcessing.SchedulerService/DataGenerator/Program.cs @@ -1,4 +1,5 @@ using System; +using SimpleResults; using TransactionProcessing.SchedulerService.DataGenerator; using TransactionProcessor.DataTransferObjects.Responses.Contract; using TransactionProcessor.DataTransferObjects.Responses.Merchant; @@ -66,7 +67,7 @@ static async Task Main(string[] args){ Guid estateId = Guid.Parse("435613ac-a468-47a3-ac4f-649d89764c22"); // Get a token to talk to the estate service - CancellationToken cancellationToken = new CancellationToken(); + CancellationToken cancellationToken = new(); String clientId = "serviceClient"; String clientSecret = "d192cbc46d834d0da90e8a9d50ded543"; ITransactionDataGeneratorService g = new TransactionDataGeneratorService(Program.SecurityServiceClient, @@ -97,14 +98,25 @@ private static async Task GenerateStatements(ITransactionDataGeneratorService g, private static async Task GenerateTransactions(ITransactionDataGeneratorService g, Guid estateId, CancellationToken cancellationToken){ // Set the date range - DateTime startDate = new DateTime(2025, 3, 1); //27/7 - DateTime endDate = new DateTime(2025, 3,2); // This is the date of the last generated transaction - - List dateRange = g.GenerateDateRange(startDate, endDate); - List allContracts = await g.GetEstateContracts(estateId, cancellationToken); - List merchants = await g.GetMerchants(estateId, cancellationToken); - - Dictionary<(String, String), Decimal> floatDeposits = new Dictionary<(String, String), Decimal> { + DateTime startDate = new DateTime(2025, 4, 14); //27/7 + DateTime endDate = new DateTime(2025, 4,30); // This is the date of the last generated transaction + + Result> dateRangeResult = g.GenerateDateRange(startDate, endDate); + if (dateRangeResult.IsFailed) + { + Console.WriteLine($"Failed to generate date range: {dateRangeResult.Message}"); + return; + } + var allContractsResult = await g.GetEstateContracts(estateId, cancellationToken); + if (allContractsResult.IsFailed) { + Console.WriteLine($"Failed to get estate contracts: {allContractsResult.Message}"); + } + var merchantsResult = await g.GetMerchants(estateId, cancellationToken); + if (merchantsResult.IsFailed) + { + Console.WriteLine($"Failed to get merchants: {merchantsResult.Message} "); + } + Dictionary<(String, String), Decimal> floatDeposits = new() { { ("Healthcare Centre 1 Contract", "10 KES Voucher"), 1400 }, { ("Healthcare Centre 1 Contract", "Custom"), 27000 }, { ("Safaricom Contract", "100 KES Topup"), 14000 }, @@ -133,14 +145,14 @@ private static async Task GenerateTransactions(ITransactionDataGeneratorService // Settlement DataToSend dataToSend = DataToSend.Settlement; - foreach (DateTime dateTime in dateRange){ + foreach (DateTime dateTime in dateRangeResult.Data){ if ((dataToSend & DataToSend.FloatDeposits) == DataToSend.FloatDeposits) { - foreach (ContractResponse contractResponse in allContracts) { + foreach (ContractResponse contractResponse in allContractsResult.Data) { foreach (ContractProduct contractResponseProduct in contractResponse.Products) { // Lookup the deposit amount here - var depositAmount = floatDeposits.SingleOrDefault(f => + KeyValuePair<(String, String), Decimal> depositAmount = floatDeposits.SingleOrDefault(f => f.Key.Item1 == contractResponse.Description && f.Key.Item2 == contractResponseProduct.Name); @@ -151,7 +163,7 @@ await g.MakeFloatDeposit(dateTime, estateId, contractResponse.ContractId, } if ((dataToSend & DataToSend.Logons) == DataToSend.Logons) { - foreach (MerchantResponse merchant in merchants) { + foreach (MerchantResponse merchant in merchantsResult.Data) { // Send a logon transaction await g.PerformMerchantLogon(dateTime, merchant, cancellationToken); @@ -160,7 +172,7 @@ await g.MakeFloatDeposit(dateTime, estateId, contractResponse.ContractId, if ((dataToSend & DataToSend.Sales) == DataToSend.Sales) { - foreach (MerchantResponse merchant in merchants) { + foreach (MerchantResponse merchant in merchantsResult.Data) { // Get the merchants contracts List contracts = await g.GetMerchantContracts(merchant, cancellationToken); foreach (ContractResponse contract in contracts) { @@ -173,7 +185,7 @@ await g.MakeFloatDeposit(dateTime, estateId, contractResponse.ContractId, } if ((dataToSend & DataToSend.Files) == DataToSend.Files) { - foreach (MerchantResponse merchant in merchants) { + foreach (MerchantResponse merchant in merchantsResult.Data) { // Get the merchants contracts List contracts = await g.GetMerchantContracts(merchant, cancellationToken); diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs index 56da55f..b5187c6 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs @@ -830,7 +830,7 @@ public static DateTime GetTransactionDateTime(Random r, DateTime dateTime) else { // Generate the time - Int32 hours = r.Next(0, 23); + Int32 hours = r.Next(9, 22); Int32 minutes = r.Next(0, 59); Int32 seconds = r.Next(0, 59); diff --git a/TransactionProcessor.SystemSetupTool/EstateSetupFunctions.cs b/TransactionProcessor.SystemSetupTool/EstateSetupFunctions.cs index 5e9d1a9..ca42ff5 100644 --- a/TransactionProcessor.SystemSetupTool/EstateSetupFunctions.cs +++ b/TransactionProcessor.SystemSetupTool/EstateSetupFunctions.cs @@ -449,11 +449,16 @@ private async Task UpdateMerchant(Merchant merchant, return ResultHelpers.CreateFailure(getContractsResult); var merchantContractsResult = await this.TransactionProcessorClient.GetMerchantContracts(this.TokenResponse.AccessToken, this.EstateId, existingMerchant.MerchantId, cancellationToken); - if (merchantContractsResult.IsFailed) + if (merchantContractsResult.IsFailed && merchantContractsResult.Status != ResultStatus.NotFound) return ResultHelpers.CreateFailure(merchantContractsResult); + List merchantContracts = merchantContractsResult.Data; + if (merchantContractsResult.Status == ResultStatus.NotFound) { + merchantContracts = new List(); + } + // Now contracts - foreach (ContractResponse contractResponse in getContractsResult.Data) { - if (merchantContractsResult.Data.SingleOrDefault(c => c.ContractId == contractResponse.ContractId) != null) + foreach (ContractResponse contractResponse in getContractsResult.Data) { + if (merchantContracts.SingleOrDefault(c => c.ContractId == contractResponse.ContractId) != null) continue; AddMerchantContractRequest addMerchantContractRequest = new() { ContractId = contractResponse.ContractId }; diff --git a/TransactionProcessor.SystemSetupTool/EventStoreFunctions.cs b/TransactionProcessor.SystemSetupTool/EventStoreFunctions.cs index 36abb14..fe3a7f9 100644 --- a/TransactionProcessor.SystemSetupTool/EventStoreFunctions.cs +++ b/TransactionProcessor.SystemSetupTool/EventStoreFunctions.cs @@ -45,6 +45,11 @@ private async Task SetupSubscriptions(CancellationToken cancellationToke ("$ce-FileImportLogAggregate", "Transaction Processor", 0), ("$ce-OperatorAggregate", "Transaction Processor", 0), + ("$ce-TransactionAggregate", "Transaction Processor - Domain", 0), + ("$ce-SettlementAggregate", "Transaction Processor - Domain", 0), + ("$ce-FloatAggregate", "Transaction Processor - Domain", 0), + ("$ce-MerchantStatementForDateAggregate", "Transaction Processor - Domain", 0), + ("$ce-EstateAggregate", "Transaction Processor - Ordered", 1), ("$ce-SettlementAggregate", "Transaction Processor - Ordered", 1), ("$ce-VoucherAggregate", "Transaction Processor - Ordered", 1), diff --git a/TransactionProcessor.SystemSetupTool/Program.cs b/TransactionProcessor.SystemSetupTool/Program.cs index b69002f..6f2b63d 100644 --- a/TransactionProcessor.SystemSetupTool/Program.cs +++ b/TransactionProcessor.SystemSetupTool/Program.cs @@ -69,7 +69,7 @@ static async Task Main(string[] args) { Mode setupMode = Mode.EstateSetup; - String configFileName = "setupconfig.json"; + String configFileName = "setupconfig.staging.json"; IdentityServerConfiguration identityServerConfiguration = await Program.GetIdentityServerConfig(cancellationToken); IdentityServerFunctions identityServerFunctions = new(Program.SecurityServiceClient, identityServerConfiguration); diff --git a/TransactionProcessor.SystemSetupTool/appsettings.json b/TransactionProcessor.SystemSetupTool/appsettings.json index 4dc3dfa..d66ba2c 100644 --- a/TransactionProcessor.SystemSetupTool/appsettings.json +++ b/TransactionProcessor.SystemSetupTool/appsettings.json @@ -1,20 +1,17 @@ { "AppSettings": { // Local (Docker) - //"EstateManagementUri": "http://127.0.0.1:5000", - "SecurityServiceUri": "https://127.0.0.1:5001", - "TransactionProcessorApi": "http://127.0.0.1:5002", - "EventStoreAddress": "esdb://admin:changeit@127.0.0.1:4113?tls=false&tlsVerifyCert=false" + //"SecurityServiceUri": "https://127.0.0.1:5001", + //"TransactionProcessorApi": "http://127.0.0.1:5002", + //"EventStoreAddress": "esdb://admin:changeit@127.0.0.1:4113?tls=false&tlsVerifyCert=false" // Staging - //"EstateManagementUri": "http://192.168.1.167:5000", - //"SecurityServiceUri": "https://192.168.1.167:5001", - //"TransactionProcessorApi": "http://192.168.1.167:5002", - //"EventStoreAddress": "esdb://admin:changeit@192.168.1.167:2113?tls=false&tlsVerifyCert=false" + "SecurityServiceUri": "https://192.168.1.167:5001", + "TransactionProcessorApi": "http://192.168.1.167:5002", + "EventStoreAddress": "esdb://admin:changeit@192.168.1.167:2113?tls=false&tlsVerifyCert=false" //"EventStoreAddress": "esdb://admin:changeit@192.168.1.157:2113?tls=false&tlsVerifyCert=false" // Production - //"EstateManagementUri": "http://192.168.1.155:5000", //"SecurityServiceUri": "https://192.168.1.155:5001", //"TransactionProcessorApi": "http://192.168.1.155:5002", //"EventStoreAddress": "esdb://admin:changeit@192.168.1.155:2113?tls=false&tlsVerifyCert=false" diff --git a/TransactionProcessor.SystemSetupTool/identityserverconfig.json b/TransactionProcessor.SystemSetupTool/identityserverconfig.json index b96470f..bdccdba 100644 --- a/TransactionProcessor.SystemSetupTool/identityserverconfig.json +++ b/TransactionProcessor.SystemSetupTool/identityserverconfig.json @@ -48,14 +48,6 @@ } ], "apiresources": [ - { - "secret": "eb47603986574b3ea2e6195a2076696e", - "name": "estateManagement", - "display_name": "Estate Management REST", - "description": "API Resource representing Estate Management REST", - "scopes": [ "estateManagement" ], - "user_claims": [ "merchantId", "estateId", "role" ] - }, { "secret": "3241913328414df4b8d031ed8f59d1bb", "name": "estateReporting", @@ -78,7 +70,7 @@ "display_name": "Transaction Processor REST", "description": "API Resource representing Transaction Processor REST", "scopes": [ "transactionProcessor" ], - "user_claims": null + "user_claims": [ "merchantId", "estateId", "role" ] }, { "secret": "bc7c25e9bf1649cc9d41abebfe98955e", @@ -103,7 +95,7 @@ "secret": "d192cbc46d834d0da90e8a9d50ded543", "client_name": "Service Client", "client_description": "Client for use in inter service communications", - "allowed_scopes": [ "transactionProcessorACL", "transactionProcessor", "estateManagement", "estateReporting", "messagingService", "fileProcessor" ], + "allowed_scopes": [ "transactionProcessorACL", "transactionProcessor", "estateReporting", "messagingService", "fileProcessor" ], "allowed_grant_types": [ "client_credentials" ] }, { @@ -111,7 +103,7 @@ "secret": "d192cbc46d834d0da90e8a9d50ded543", "client_name": "Management UI App Client", "client_description": "Client for use by management app", - "allowed_scopes": [ "fileProcessor", "estateManagement", "transactionProcessor", "openid", "email", "profile" ], + "allowed_scopes": [ "fileProcessor", "transactionProcessor", "openid", "email", "profile" ], "allowed_grant_types": [ "hybrid", "password" @@ -129,16 +121,6 @@ "require_consent": true, "allow_offline_access": true }, - { - "client_id": "voucherAppClient", - "secret": "d09bdc23b50f4582aca09134268d5600", - "client_name": "Voucher Mobile App Client", - "client_description": "Client for use by voucher redemption mobile app to process voucher redemptions", - "allowed_scopes": [ "voucherManagementACL" ], - "allowed_grant_types": [ - "password" - ] - }, { "client_id": "mobileAppClient", "secret": "d192cbc46d834d0da90e8a9d50ded543", @@ -151,11 +133,6 @@ } ], "apiscopes": [ - { - "description": "Scope for Estate Management REST", - "displayName": "Estate Management REST", - "name": "estateManagement" - }, { "description": "Scope for Estate Reporting REST", "displayName": "Estate Reporting REST",