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
11 changes: 11 additions & 0 deletions db/query_statistics.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SELECT customer.customername AS customer,
TO_CHAR(appointment."yearMonth", 'Month YYYY') AS month,
appointment.count AS appointments,
participant.count AS participants,
voting.count AS votings
FROM "appointmentStatistic" appointment
LEFT JOIN "participantStatistic" participant ON appointment."customerId" = participant."customerId"
LEFT JOIN "votingStatistic" voting ON appointment."customerId" = voting."customerId"
LEFT JOIN customer ON appointment."customerId" = customer.customerid
WHERE appointment."customerId" = '11111111-1111-1111-1111-111111111111'
ORDER BY appointment."yearMonth" DESC;

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Dataport.Terminfinder.BusinessLayer;
public class AppointmentBusinessLayer : BusinessLayerBase, IAppointmentBusinessLayer
{
private readonly IAppointmentRepository _appointmentRepo;
private readonly IStatisticRepository _statisticRepo;
private readonly IBcryptWrapper _bcryptWrapper;
private static readonly int MaxCountOfElementsOfParticipants = 5000;
private static readonly int MaxCountOfElementsOfSuggestedDates = 100;
Expand All @@ -19,10 +20,12 @@ public class AppointmentBusinessLayer : BusinessLayerBase, IAppointmentBusinessL
/// constructor
/// </summary>
/// <param name="appointmentRepo">Appointment repository</param>
/// <param name="statisticRepo">Statistic repository</param>
/// <param name="customerRepo">Customer repository</param>
/// <param name="bcryptWrapper">BCryptWrapper</param>
/// <param name="logger">Logger</param>
public AppointmentBusinessLayer(IAppointmentRepository appointmentRepo,
IStatisticRepository statisticRepo,
ICustomerRepository customerRepo,
IBcryptWrapper bcryptWrapper,
ILogger<AppointmentBusinessLayer> logger)
Expand All @@ -31,6 +34,7 @@ public AppointmentBusinessLayer(IAppointmentRepository appointmentRepo,
Logger.LogDebug($"Enter {nameof(AppointmentBusinessLayer)}");

_appointmentRepo = appointmentRepo;
_statisticRepo = statisticRepo;
_bcryptWrapper = bcryptWrapper ?? throw new ArgumentNullException(nameof(bcryptWrapper));
}

Expand Down Expand Up @@ -185,6 +189,7 @@ public Appointment AddAppointment(Appointment appointment)
if (appointment != null && ExistsCustomer(appointment.CustomerId))
{
HashPasswordInAppointment(appointment);
_statisticRepo.IncrementAppointmentStatisticCount(appointment.CustomerId);
_appointmentRepo.AddAndUpdateAppointment(appointment);
appointment = _appointmentRepo.GetAppointment(appointment.CustomerId, appointment.AppointmentId);
}
Expand Down Expand Up @@ -290,6 +295,17 @@ public ICollection<Participant> AddAndUpdateParticipants(Guid customerId, Guid a
return (participants);
}

if (participants.Any(participant => participant.ParticipantId == Guid.Empty))
{
_statisticRepo.IncrementParticipantStatisticCount(customerId);
}

var newVotingCount = participants.Sum(p => p.Votings?.Count(v => v.VotingId == Guid.Empty) ?? 0);
if (newVotingCount > 0)
{
_statisticRepo.IncrementVotingStatisticCount(customerId, newVotingCount);
}

_appointmentRepo.AddAndUpdateParticipants(participants);
participants = _appointmentRepo.GetParticipants(customerId, appointmentId);
return (participants);
Expand Down
35 changes: 35 additions & 0 deletions src/Dataport.Terminfinder.BusinessObject/AppointmentStatistic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Dataport.Terminfinder.BusinessObject;

/// <summary>
/// Representation for an appointment statistic
/// </summary>
[ExcludeFromCodeCoverage]
[Table("appointmentStatistic", Schema = "public")]
public class AppointmentStatistic
{
/// <summary>
/// Identifier of the appointment statistic
/// </summary>
[Column("appointmentStatisticId")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid AppointmentStatisticId { get; set; }

/// <summary>
/// Identifier of the customer
/// </summary>
/// <example>00000000-0000-0000-0000-000000000000</example>
[Column("customerId")]
public Guid CustomerId { get; set; }

/// <summary>
/// The year and month for which the statistic is recorded
/// </summary>
[Column("yearMonth")]
public DateOnly YearMonth { get; set; }

/// <summary>
/// The count for the recorded statistic
/// </summary>
[Column("count")]
public int Count { get; set; } = 1;
}
35 changes: 35 additions & 0 deletions src/Dataport.Terminfinder.BusinessObject/ParticipantStatistic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Dataport.Terminfinder.BusinessObject;

/// <summary>
/// Representation for a Participant statistic
/// </summary>
[ExcludeFromCodeCoverage]
[Table("participantStatistic", Schema = "public")]
public class ParticipantStatistic
{
/// <summary>
/// Identifier of the participant statistic
/// </summary>
[Column("participantStatisticId")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ParticipantStatisticId { get; set; }

/// <summary>
/// Identifier of the customer
/// </summary>
/// <example>00000000-0000-0000-0000-000000000000</example>
[Column("customerId")]
public Guid CustomerId { get; set; }

/// <summary>
/// The year and month for which the statistic is recorded
/// </summary>
[Column("yearMonth")]
public DateOnly YearMonth { get; set; }

/// <summary>
/// The count for the recorded statistic
/// </summary>
[Column("count")]
public int Count { get; set; } = 1;
}
35 changes: 35 additions & 0 deletions src/Dataport.Terminfinder.BusinessObject/VotingStatistic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Dataport.Terminfinder.BusinessObject;

/// <summary>
/// Representation for a Voting statistic
/// </summary>
[ExcludeFromCodeCoverage]
[Table("votingStatistic", Schema = "public")]
public class VotingStatistic
{
/// <summary>
/// Identifier of the voting statistic
/// </summary>
[Column("votingStatisticId")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid VotingStatisticId { get; set; }

/// <summary>
/// Identifier of the customer
/// </summary>
/// <example>00000000-0000-0000-0000-000000000000</example>
[Column("customerId")]
public Guid CustomerId { get; set; }

/// <summary>
/// The year and month for which the statistic is recorded
/// </summary>
[Column("yearMonth")]
public DateOnly YearMonth { get; set; }

/// <summary>
/// The count for the recorded statistic
/// </summary>
[Column("count")]
public int Count { get; set; } = 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</PackageReference>
<PackageReference Include="JetBrains.Annotations" Version="2026.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageReference Include="Moq" Version="4.20.72" />
Expand Down
155 changes: 155 additions & 0 deletions src/Dataport.Terminfinder.Repository.Tests/StatisticRepositoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using Microsoft.Data.Sqlite;

namespace Dataport.Terminfinder.Repository.Tests;

[TestClass]
public class StatisticRepositoryTests
{
private static readonly Guid ExpectedCustomerId = Guid.Parse("BE1D657A-4D06-40DB-8443-D67BBB950EE7");
private static readonly DateOnly ExpectedYearMonth = new(2026, 9, 1);
private const int ExpectedInitialAppointmentCount = 2;
private const int ExpectedInitialParticipantCount = 4;
private const int ExpectedInitialVotingCount = 2;
private const int ExpectedIncrementedAppointmentCount = ExpectedInitialAppointmentCount + 1;
private const int ExpectedIncrementedParticipantCount = ExpectedInitialParticipantCount + 1;
private const int ExpectedIncrementedVotingCount = ExpectedInitialVotingCount + 5;

[TestMethod]
public void IncrementAppointmentStatisticCount_WhenStatisticIsMissing_CreatesStatisticWithExpectedCount()
{
using var context = CreateContext();
var sut = CreateSut(context);

sut.IncrementAppointmentStatisticCount(ExpectedCustomerId);

var statistic = context.AppointmentStatistics.Single();
Assert.AreEqual(ExpectedCustomerId, statistic.CustomerId);
Assert.AreEqual(ExpectedYearMonth, statistic.YearMonth);
Assert.AreEqual(1, statistic.Count);
}

[TestMethod]
public void IncrementAppointmentStatisticCount_WhenStatisticExists_IncreasesCountByOne()
{
using var context = CreateContext();
AddAppointmentStatistic(context, ExpectedInitialAppointmentCount);
var sut = CreateSut(context);

sut.IncrementAppointmentStatisticCount(ExpectedCustomerId);

var statistic = context.AppointmentStatistics.Single();
Assert.AreEqual(ExpectedIncrementedAppointmentCount, statistic.Count);
}

[TestMethod]
public void IncrementParticipantStatisticCount_WhenStatisticIsMissing_CreatesStatisticWithExpectedCount()
{
using var context = CreateContext();
var sut = CreateSut(context);

sut.IncrementParticipantStatisticCount(ExpectedCustomerId);

var statistic = context.ParticipantStatistics.Single();
Assert.AreEqual(ExpectedCustomerId, statistic.CustomerId);
Assert.AreEqual(ExpectedYearMonth, statistic.YearMonth);
Assert.AreEqual(1, statistic.Count);
}

[TestMethod]
public void IncrementParticipantStatisticCount_WhenStatisticExists_IncreasesCountByOne()
{
using var context = CreateContext();
AddParticipantStatistic(context, ExpectedInitialParticipantCount);
var sut = CreateSut(context);

sut.IncrementParticipantStatisticCount(ExpectedCustomerId);

var statistic = context.ParticipantStatistics.Single();
Assert.AreEqual(ExpectedIncrementedParticipantCount, statistic.Count);
}

[TestMethod]
public void IncrementVotingStatisticCount_WhenStatisticIsMissing_CreatesStatisticWithExpectedCount()
{
using var context = CreateContext();
var sut = CreateSut(context);

sut.IncrementVotingStatisticCount(ExpectedCustomerId, 3);

var statistic = context.VotingStatistics.Single();
Assert.AreEqual(ExpectedCustomerId, statistic.CustomerId);
Assert.AreEqual(ExpectedYearMonth, statistic.YearMonth);
Assert.AreEqual(3, statistic.Count);
}

[TestMethod]
public void IncrementVotingStatisticCount_WhenStatisticExists_IncreasesCountByProvidedValue()
{
using var context = CreateContext();
AddVotingStatistic(context, ExpectedInitialVotingCount);
var sut = CreateSut(context);

sut.IncrementVotingStatisticCount(ExpectedCustomerId, 5);

var statistic = context.VotingStatistics.Single();
Assert.AreEqual(ExpectedIncrementedVotingCount, statistic.Count);
}

[TestMethod]
public void IncrementVotingStatisticCount_WhenCountIsNotPositive_DoesNotCreateAnyExpectedStatistic()
{
using var context = CreateContext();
var sut = CreateSut(context);

sut.IncrementVotingStatisticCount(ExpectedCustomerId, 0);
sut.IncrementVotingStatisticCount(ExpectedCustomerId, -1);

Assert.AreEqual(0, context.VotingStatistics.Count());
}

private static StatisticRepository CreateSut(DataContext context)
{
return new StatisticRepository(context, Mock.Of<ILogger<StatisticRepository>>());
}

private static void AddAppointmentStatistic(DataContext context, int count)
{
context.AppointmentStatistics.Add(
new AppointmentStatistic { CustomerId = ExpectedCustomerId, YearMonth = ExpectedYearMonth, Count = count }
);
context.SaveChanges();
context.ChangeTracker.Clear();
}

private static void AddParticipantStatistic(DataContext context, int count)
{
context.ParticipantStatistics.Add(
new ParticipantStatistic { CustomerId = ExpectedCustomerId, YearMonth = ExpectedYearMonth, Count = count }
);
context.SaveChanges();
context.ChangeTracker.Clear();
}

private static void AddVotingStatistic(DataContext context, int count)
{
context.VotingStatistics.Add(
new VotingStatistic { CustomerId = ExpectedCustomerId, YearMonth = ExpectedYearMonth, Count = count }
);
context.SaveChanges();
context.ChangeTracker.Clear();
}

private static DataContext CreateContext()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();

var options = new DbContextOptionsBuilder<DataContext>()
.UseSqlite(connection)
.Options;

var context = new DataContext(options, LoggerFactory.Create(_ => { }));
context.Database.EnsureCreated();
return context;
}
}
Loading