Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions Scripts/Sales Reconciliation.js
Original file line number Diff line number Diff line change
@@ -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];
}
42 changes: 27 additions & 15 deletions TransactionProcessing.SchedulerService/DataGenerator/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using SimpleResults;
using TransactionProcessing.SchedulerService.DataGenerator;
using TransactionProcessor.DataTransferObjects.Responses.Contract;
using TransactionProcessor.DataTransferObjects.Responses.Merchant;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<DateTime> dateRange = g.GenerateDateRange(startDate, endDate);
List<ContractResponse> allContracts = await g.GetEstateContracts(estateId, cancellationToken);
List<MerchantResponse> 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<List<DateTime>> 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 },
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand All @@ -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<ContractResponse> contracts = await g.GetMerchantContracts(merchant, cancellationToken);
foreach (ContractResponse contract in contracts) {
Expand All @@ -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<ContractResponse> contracts = await g.GetMerchantContracts(merchant, cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
11 changes: 8 additions & 3 deletions TransactionProcessor.SystemSetupTool/EstateSetupFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,11 +449,16 @@ private async Task<Result> 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<ContractResponse> merchantContracts = merchantContractsResult.Data;
if (merchantContractsResult.Status == ResultStatus.NotFound) {
merchantContracts = new List<ContractResponse>();
}

// 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 };
Expand Down
5 changes: 5 additions & 0 deletions TransactionProcessor.SystemSetupTool/EventStoreFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ private async Task<Result> 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),
Expand Down
2 changes: 1 addition & 1 deletion TransactionProcessor.SystemSetupTool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 6 additions & 9 deletions TransactionProcessor.SystemSetupTool/appsettings.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
29 changes: 3 additions & 26 deletions TransactionProcessor.SystemSetupTool/identityserverconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -103,15 +95,15 @@
"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" ]
},
{
"client_id": "managementUIClient",
"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"
Expand All @@ -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",
Expand All @@ -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",
Expand Down