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
98 changes: 85 additions & 13 deletions .github/workflows/createrelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
run: |
dotnet publish "TransactionProcessor.HealthChecksUI/TransactionProcessor.HealthChecksUI/TransactionProcessor.HealthChecksUI.csproj" --configuration Release --output TransactionProcessor.HealthChecksUI/publishOutput -r linux-x64 --self-contained
dotnet publish "TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.csproj" --configuration Release --output TransactionProcessing.SchedulerService/publishOutput -r linux-x64 --self-contained
dotnet publish "TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.TickerQ.csproj" --configuration Release --output TransactionProcessing.SchedulerService.TickerQ/publishOutput -r linux-x64 --self-contained

- name: Build Release Package (Health Check UI)
run: |
Expand All @@ -51,6 +52,12 @@ jobs:
zip -r ../schedulerservice.zip ./*
echo "Zip file created at: $(realpath ../schedulerservice.zip)"

- name: Build Release Package (Scheduler Service TickerQ)
run: |
cd /home/runner/work/SupportTools/SupportTools/TransactionProcessing.SchedulerService.TickerQ/publishOutput
zip -r ../schedulerservicetq.zip ./*
echo "Zip file created at: $(realpath ../schedulerservicetq.zip)"

- name: Upload the artifact (Health Check UI)
uses: actions/upload-artifact@v4.4.0
with:
Expand All @@ -62,6 +69,12 @@ jobs:
with:
name: schedulerservice
path: /home/runner/work/SupportTools/SupportTools/TransactionProcessing.SchedulerService/schedulerservice.zip

- name: Upload the artifact (Scheduler Service Ticker Q)
uses: actions/upload-artifact@v4.4.0
with:
name: schedulerservicetq
path: /home/runner/work/SupportTools/SupportTools/TransactionProcessing.SchedulerService.TickerQ/schedulerservicetq.zip

deploystaging:
runs-on: [stagingserver, linux]
Expand Down Expand Up @@ -142,15 +155,74 @@ jobs:
sudo systemctl start "$SERVICE_NAME"
sudo systemctl status "$SERVICE_NAME" --no-pager # For debugging/verification

- name: Download the artifact (Scheduler Service)
# - name: Download the artifact (Scheduler Service)
# uses: actions/download-artifact@v4.1.8
# with:
# name: schedulerservice
# path: /tmp/supporttools/schedulerservice

# - name: Remove existing service (Scheduler Service)
# run: |
# SERVICE_NAME="schedulerservice"
# if systemctl is-active --quiet "$SERVICE_NAME"; then
# echo "Stopping existing service..."
# sudo systemctl stop "$SERVICE_NAME"
# fi
# if systemctl is-enabled --quiet "$SERVICE_NAME"; then
# echo "Disabling existing service..."
# sudo systemctl disable "$SERVICE_NAME"
# fi
# if [ -f "/etc/systemd/system/${SERVICE_NAME}.service" ]; then
# echo "Removing existing service unit file..."
# sudo rm "/etc/systemd/system/${SERVICE_NAME}.service"
# sudo systemctl daemon-reload
# fi

# - name: Unzip the files (Scheduler Service)
# run: |
# sudo mkdir -p /opt/txnproc/transactionprocessing/supporttools/schedulerservice
# sudo unzip -o /tmp/supporttools/schedulerservice/schedulerservice.zip -d /opt/txnproc/transactionprocessing/supporttools/schedulerservice

# - name: Install and Start as a Linux service (Scheduler Service)
# run: |
# SERVICE_NAME="schedulerservice"
# # The WorkingDirectory is crucial for .NET apps to find appsettings.json and other files
# WORKING_DIRECTORY="/opt/txnproc/transactionprocessing/supporttools/schedulerservice"
# DLL_NAME="TransactionProcessing.SchedulerService.dll" # Your application's DLL
# SERVICE_DESCRIPTION="Transaction Processing - Scheduler Service"

# # Create a systemd service file
# echo "[Unit]" | sudo tee /etc/systemd/system/${SERVICE_NAME}.service
# echo "Description=${SERVICE_DESCRIPTION}" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "After=network.target" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "[Service]" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# # IMPORTANT: Use 'dotnet' to run your DLL
# echo "ExecStart=/usr/bin/dotnet ${WORKING_DIRECTORY}/${DLL_NAME}" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "WorkingDirectory=${WORKING_DIRECTORY}" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "Restart=always" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "User=youruser" # IMPORTANT: Change to a dedicated, less privileged user
# echo "Group=yourgroup" # IMPORTANT: Change to a dedicated, less privileged group
# echo "Environment=ASPNETCORE_ENVIRONMENT=Production" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service # Example
# echo "" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "[Install]" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service
# echo "WantedBy=multi-user.target" | sudo tee -a /etc/systemd/system/${SERVICE_NAME}.service

# # Reload systemd, enable, and start the service
# sudo systemctl daemon-reload
# sudo systemctl enable "$SERVICE_NAME"
# sudo systemctl start "$SERVICE_NAME"
# sudo systemctl status "$SERVICE_NAME" --no-pager # For debugging/verification

- name: Download the artifact (Scheduler Service TickerQ)
uses: actions/download-artifact@v4.1.8
with:
name: schedulerservice
path: /tmp/supporttools/schedulerservice
name: schedulerservicetq
path: /tmp/supporttools/schedulerservicetq

- name: Remove existing service (Scheduler Service)
- name: Remove existing service (Scheduler Service TickerQ)
run: |
SERVICE_NAME="schedulerservice"
SERVICE_NAME="schedulerservicetq"
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo "Stopping existing service..."
sudo systemctl stop "$SERVICE_NAME"
Expand All @@ -165,18 +237,18 @@ jobs:
sudo systemctl daemon-reload
fi

- name: Unzip the files (Scheduler Service)
- name: Unzip the files (Scheduler Service TickerQ)
run: |
sudo mkdir -p /opt/txnproc/transactionprocessing/supporttools/schedulerservice
sudo unzip -o /tmp/supporttools/schedulerservice/schedulerservice.zip -d /opt/txnproc/transactionprocessing/supporttools/schedulerservice
sudo mkdir -p /opt/txnproc/transactionprocessing/supporttools/schedulerservicetq
sudo unzip -o /tmp/supporttools/schedulerservicetq/schedulerservicetq.zip -d /opt/txnproc/transactionprocessing/supporttools/schedulerservicetq

- name: Install and Start as a Linux service (Scheduler Service)
- name: Install and Start as a Linux service (Scheduler Service TickerQ)
run: |
SERVICE_NAME="schedulerservice"
SERVICE_NAME="schedulerservicetq"
# The WorkingDirectory is crucial for .NET apps to find appsettings.json and other files
WORKING_DIRECTORY="/opt/txnproc/transactionprocessing/supporttools/schedulerservice"
DLL_NAME="TransactionProcessing.SchedulerService.dll" # Your application's DLL
SERVICE_DESCRIPTION="Transaction Processing - Scheduler Service"
WORKING_DIRECTORY="/opt/txnproc/transactionprocessing/supporttools/schedulerservicetq"
DLL_NAME="TransactionProcessing.SchedulerService.TickerQ.dll" # Your application's DLL
SERVICE_DESCRIPTION="Transaction Processing - Scheduler Service Ticker Q"

# Create a systemd service file
echo "[Unit]" | sudo tee /etc/systemd/system/${SERVICE_NAME}.service
Expand Down
27 changes: 16 additions & 11 deletions TransactionProcessing.SchedulerService/DataGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static async Task Main(string[] args){
HttpClient httpClient = new HttpClient(handler);

baseAddressFunc = (apiName) => {
String ipaddress = "192.168.1.167";
String ipaddress = "192.168.1.163";

if (apiName == "SecurityService"){
return $"https://{ipaddress}:5001";
Expand Down Expand Up @@ -104,8 +104,8 @@ 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, 4, 16); //27/7
DateTime endDate = new DateTime(2025, 4,16); // This is the date of the last generated transaction
DateTime startDate = new DateTime(2025, 7, 2); //27/7
DateTime endDate = new DateTime(2025, 8,27); // This is the date of the last generated transaction

Result<List<DateTime>> dateRangeResult = g.GenerateDateRange(startDate, endDate);
if (dateRangeResult.IsFailed)
Expand All @@ -132,24 +132,29 @@ private static async Task GenerateTransactions(ITransactionDataGeneratorService
{ ("PataPawa prePay Contract", "Pre Pay Bill Pay"), 18000 }
};


DataToSend dataToSend = 0;
// Everything
//DataToSend dataToSend = DataToSend.FloatDeposits | DataToSend.Logons | DataToSend.Sales | DataToSend.Files | DataToSend.Settlement;
//dataToSend = DataToSend.FloatDeposits | DataToSend.Logons | DataToSend.Sales | DataToSend.Files | DataToSend.Settlement;

// Everything (No settlement)
//DataToSend dataToSend = DataToSend.FloatDeposits | DataToSend.Logons | DataToSend.Sales | DataToSend.Files;
dataToSend = DataToSend.FloatDeposits | DataToSend.Logons | DataToSend.Sales | DataToSend.Files;

// Floats
//DataToSend dataToSend = DataToSend.FloatDeposits;
//dataToSend = DataToSend.FloatDeposits;

// Logons and Sales
//DataToSend dataToSend = DataToSend.Logons | DataToSend.Sales;
//dataToSend = DataToSend.Logons | DataToSend.Sales;

// Files
//DataToSend dataToSend = DataToSend.Files;
//dataToSend = DataToSend.Files;

// Settlement
DataToSend dataToSend = DataToSend.Settlement;
dataToSend = DataToSend.Settlement;

if (dataToSend == 0) {
Console.WriteLine("No data to send");
return;
}

foreach (DateTime dateTime in dateRangeResult.Data){

Expand Down Expand Up @@ -205,7 +210,7 @@ await g.MakeFloatDeposit(dateTime, estateId, contractResponse.ContractId,
}
foreach (ContractResponse contract in getMerchantContractsResult.Data) {
// Generate a file and upload
await g.SendUploadFile(dateTime, contract, merchant, Guid.Empty, cancellationToken);
await g.SendUploadFile(dateTime, contract, merchant, Guid.Parse("75e19f2e-2ce9-4296-930a-3bb4416375f4"), cancellationToken);

await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public interface ITransactionDataGeneratorService
Task<Result<List<MerchantResponse>>> GetMerchants(Guid estateId, CancellationToken cancellationToken);
Task<Result<SerialisedMessage>> PerformMerchantLogon(DateTime dateTime, MerchantResponse merchant, CancellationToken cancellationToken);
Task<Result> PerformSettlement(DateTime dateTime, Guid estateId, CancellationToken cancellationToken);

Task<Result> PerformMerchantSettlement(DateTime dateTime,
Guid estateId,
Guid merchantId,
CancellationToken cancellationToken);
Task<Result> SendSales(DateTime dateTime, MerchantResponse merchant, ContractResponse contract, Int32 numberOfSales, CancellationToken cancellationToken);
Task<Result> SendUploadFile(DateTime dateTime, ContractResponse contract, MerchantResponse merchant, Guid userId, CancellationToken cancellationToken);
Task<Result<MerchantResponse>> GetMerchant(Guid estateId, Guid merchantId, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
private readonly String TransactionProcessorApi;
private readonly String FileProcessorApi;

public TransactionDataGeneratorService(ISecurityServiceClient securityServiceClient,

Check warning on line 24 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

Non-nullable field 'ClientToken' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 24 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

Non-nullable field 'TokenResponse' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
ITransactionProcessorClient transactionProcessorClient,
String transactionProcessorApi,
String fileProcessorApi,
Expand Down Expand Up @@ -180,6 +180,23 @@
return Result.Success();
}

public async Task<Result> PerformMerchantSettlement(DateTime dateTime,
Guid estateId,
Guid merchantId,
CancellationToken cancellationToken) {
this.WriteTrace($"About to send Process Settlement Request for Date [{dateTime:dd-MM-yyyy}] and Estate [{estateId}] and Merchant [{merchantId}]");
Result result = await this.SendProcessSettlementRequest(dateTime, estateId, merchantId, cancellationToken);
if (result.IsFailed) {
this.WriteError($"Error sending Process Settlement Request for Date [{dateTime:dd-MM-yyyy}] and Estate [{estateId}] and MerchantId [{merchantId}]");
this.WriteError(result.Message);
return Result.Failure($"Error sending Process Settlement Request for Date [{dateTime:dd-MM-yyyy}] and Estate [{estateId}] and MerchantId [{merchantId}]");
}

this.WriteTrace($"Process Settlement Request sent for Date [{dateTime:dd-MM-yyyy}] and Estate [{estateId}] and Merchant [{merchantId}]");

return Result.Success();
}

public async Task<Result> SendSales(DateTime dateTime,
MerchantResponse merchant,
ContractResponse contract,
Expand All @@ -194,11 +211,11 @@
{
this.WriteTrace($"product [{contractProduct.DisplayText}]");

List<(SaleTransactionRequest request, Decimal amount)> saleRequests = null;

Check warning on line 214 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

Converting null literal or possible null value to non-nullable type.
// Get a number of sales to be sent
if (numberOfSales == 0)
{
numberOfSales = this.r.Next(20, 25);
numberOfSales = this.r.Next(2, 10);
}

for (Int32 i = 1; i <= numberOfSales; i++)
Expand Down Expand Up @@ -260,7 +277,7 @@
{
sale.TransactionNumber = this.GetTransactionNumber().ToString();
Result<SerialisedMessage> saleResult = await this.SendSaleTransaction(merchant, sale, cancellationToken);
if (saleResult.IsFailed)
if (saleResult.IsSuccess)
{
salesSent++;
}
Expand All @@ -271,7 +288,7 @@
// 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();
}

Expand Down Expand Up @@ -338,7 +355,7 @@
};

request.Headers.Authorization = new AuthenticationHeaderValue("bearer", tokenResult.Data);
HttpResponseMessage response = null;

Check warning on line 358 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

Converting null literal or possible null value to non-nullable type.

try
{
Expand All @@ -359,7 +376,7 @@
}
}

private static async Task<Guid> GetFileProfileIdFromOperator(String operatorName, CancellationToken cancellationToken)

Check warning on line 379 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
// TODO: get this profile list from API

Expand Down Expand Up @@ -461,7 +478,7 @@

private String TestHostApi;

public static Decimal GetAmount(Random r, ContractProduct product = null)

Check warning on line 481 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

Cannot convert null literal to non-nullable reference type.
{
return product switch
{
Expand Down Expand Up @@ -732,7 +749,7 @@
SaleTransactionRequest request,
CancellationToken cancellationToken) {
if (this.RunningMode == RunningMode.WhatIf) {
this.WriteTrace($"Send Sale for Merchant [{merchant.MerchantName}] - {request.TransactionNumber} - {request.OperatorId} - {request.GetAmount()}");
this.WriteTrace($"Send Sale for Merchant [{merchant.MerchantName}] - {request.TransactionDateTime:yyyy-MM-dd HH:mm:ss} {request.TransactionNumber} - {request.OperatorId} - {request.GetAmount()}");
return Result.Success();
}

Expand All @@ -741,7 +758,7 @@
return ResultHelpers.CreateFailure(tokenResult);

SerialisedMessage requestSerialisedMessage = request.CreateSerialisedMessage();
SerialisedMessage responseSerialisedMessage = null;

Check warning on line 761 in TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.DataGenerator/TransactionDataGeneratorService.cs

View workflow job for this annotation

GitHub Actions / Build and Test Pull Requests

Converting null literal or possible null value to non-nullable type.

this.WriteTrace($"About to Send sale for Merchant [{merchant.MerchantName}]");
Result<SerialisedMessage> result = new Result<SerialisedMessage>();
Expand Down Expand Up @@ -805,7 +822,19 @@
}
}

private void WriteMessageX(String message,
TraceEventArgs.Level traceLevel)
{
if (this.TraceGenerated != null)
{
TraceEventArgs args = new() { TraceLevel = traceLevel, Message = message };

this.TraceGenerated.Invoke(args);
}
}

private void WriteTrace(String message) => this.WriteMessage(message, TraceEventArgs.Level.Trace);
private void WriteTraceX(String message) => this.WriteMessageX(message, TraceEventArgs.Level.Trace);
private void WriteWarning(String message) => this.WriteMessage(message, TraceEventArgs.Level.Warning);
private void WriteError(String message) => this.WriteMessage(message, TraceEventArgs.Level.Error);
private void WriteError(Exception ex) => this.WriteMessage(ex.ToString(), TraceEventArgs.Level.Error);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
 public record BaseConfiguration(
String ClientId,
String ClientSecret,
String FileProcessorApi,
String SecurityService,
String TestHostApi,
String TransactionProcessorApi,
String EventStoreAddress);


public class ReplayParkedQueueJobConfiguration {

}

public class MakeFloatCreditsJobConfiguration{

public Guid EstateId { get; set; }
public List<DepositAmount> DepositAmounts { get; set; } = new List<DepositAmount>();
}


public class DepositAmount{
public Guid ContractId { get; set; }
public Guid ProductId { get; set; }
public Decimal Amount { get; set; }
}

public class UploadTransactionFileJobConfiguration
{
public Guid EstateId { get; set; }
public Guid MerchantId { get; set; }
public Guid UserId { get; set; }
public List<String> ContractsToInclude { get; set; }
}

public class GenerateTransactionsJobConfiguration
{
public Guid EstateId { get; set; }
public Guid MerchantId { get; set; }
}

public class ProcessSettlementJobConfiguration
{
public Guid EstateId { get; set; }
public Guid MerchantId { get; set; }
}

public class MerchantStatementJobConfiguration
{
public Guid EstateId { get; set; }
public Guid MerchantId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore;
using TickerQ.EntityFrameworkCore.Configurations;

namespace TransactionProcessing.SchedulerService.TickerQ.Database
{
public class SchedulerContext : DbContext
{
public SchedulerContext(DbContextOptions<SchedulerContext> options)
: base(options) { }

protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);

// Apply TickerQ entity configurations explicitly
builder.ApplyConfiguration(new TimeTickerConfigurations());
builder.ApplyConfiguration(new CronTickerConfigurations());
builder.ApplyConfiguration(new CronTickerOccurrenceConfigurations());

// Alternatively, apply all configurations from assembly:
// builder.ApplyConfigurationsFromAssembly(typeof(TimeTickerConfigurations).Assembly);
}
}
}
Loading
Loading