diff --git a/Api/DependencyInjection.cs b/Api/DependencyInjection.cs index bbfc173..8335c4b 100644 --- a/Api/DependencyInjection.cs +++ b/Api/DependencyInjection.cs @@ -56,7 +56,7 @@ public static void AddApplication(this IServiceCollection services, IConfigurati services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Application/Extensions/MinutesConverterExtensions.cs b/Application/Extensions/MinutesConverterExtensions.cs new file mode 100644 index 0000000..58db0f0 --- /dev/null +++ b/Application/Extensions/MinutesConverterExtensions.cs @@ -0,0 +1,7 @@ +public static class MinutesConverterExtensions +{ + public static decimal ToHoursWithoutRounding(this int minutes) + { + return minutes / 60m; + } +} diff --git a/Application/Extensions/MinutesConverterExtensionsTests.cs b/Application/Extensions/MinutesConverterExtensionsTests.cs new file mode 100644 index 0000000..64d2f3a --- /dev/null +++ b/Application/Extensions/MinutesConverterExtensionsTests.cs @@ -0,0 +1,36 @@ +using Core; +using Xunit; + +[UnitTest] +public class MinutesConverterExtensionsTests +{ + [Fact] + public void ToHoursWithoutRounding_ShouldReturnCorrectValueFor60Minutes() + { + int minutes = 60; + + decimal hours = minutes.ToHoursWithoutRounding(); + + Assert.Equal(1m, hours); + } + + [Fact] + public void ToHoursWithoutRounding_ShouldReturnCorrectValueFor90Minutes() + { + int minutes = 90; + + decimal hours = minutes.ToHoursWithoutRounding(); + + Assert.Equal(1.5m, hours); + } + + [Fact] + public void ToHoursWithoutRounding_ShouldReturnCorrectValueFor430Minutes() + { + int minutes = 430; + + decimal hours = minutes.ToHoursWithoutRounding(); + + Assert.Equal(7.1666666666666666666666666667m, hours); + } +} diff --git a/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandler.cs b/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandler.cs index 93b770c..81adeb8 100644 --- a/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandler.cs +++ b/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandler.cs @@ -4,10 +4,10 @@ namespace Application.Features.Internal.GetEmployeesTrackedTaskHours; public class GetEmployeesTrackedTaskHoursHandler { - private readonly GetTaskEntriesQuery _getTaskEntriesQuery; + private readonly IGetTaskEntriesQuery _getTaskEntriesQuery; public GetEmployeesTrackedTaskHoursHandler( - GetTaskEntriesQuery getTaskEntriesQuery + IGetTaskEntriesQuery getTaskEntriesQuery ) { _getTaskEntriesQuery = getTaskEntriesQuery; @@ -32,7 +32,7 @@ DateOnly endDate x => new EmployeeTrackedTaskHourDto { EmployeeId = x.EmployeeId, - TrackedHours = x.TrackedMinutes / 60, + TrackedHours = x.TrackedMinutes.ToHoursWithoutRounding(), }) .ToList(); diff --git a/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandlerTests.cs b/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandlerTests.cs new file mode 100644 index 0000000..adb3d1a --- /dev/null +++ b/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetEmployeesTrackedTaskHoursHandlerTests.cs @@ -0,0 +1,50 @@ +using Core; +using Core.Entities; +using Moq; +using Xunit; + +namespace Application.Features.Internal.GetEmployeesTrackedTaskHours; + +[UnitTest] +public class GetEmployeesTrackedTaskHoursHandlerTests +{ + [Fact] + public async Task GetEmployeesTrackedTaskHoursHandler_ShouldReturnCorrectResultWithoutRoundingTrackedHours() + { + const long projectId = 1; + const long employeeId = 1; + + var taskEntries = new List{ + new TaskEntry + { + EmployeeId = employeeId, + ProjectId = projectId, + StartTime = new DateTime(2025, 11, 24, 10, 0, 0), + EndTime = new DateTime(2025, 11, 24, 10, 30, 0), + }, + new TaskEntry + { + EmployeeId = employeeId, + ProjectId = projectId, + StartTime = new DateTime(2025, 11, 24, 11, 0, 0), + EndTime = new DateTime(2025, 11, 24, 11, 40, 0), + }, + }; + + var getTaskEntriesQueryMock = new Mock(); + + getTaskEntriesQueryMock + .Setup(x => x.GetAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(taskEntries); + + var getEmployeesTrackedTaskHoursHandler = new GetEmployeesTrackedTaskHoursHandler( + getTaskEntriesQueryMock.Object + ); + + var result = await getEmployeesTrackedTaskHoursHandler.HandleAsync(projectId, new DateOnly(2025, 11, 1), new DateOnly(2025, 11, 28)); + + Assert.NotEmpty(result.EmployeesTrackedTaskHours); + Assert.Equal(1.1666666666666666666666666667m, result.EmployeesTrackedTaskHours[0].TrackedHours); + Assert.Equal(employeeId, result.EmployeesTrackedTaskHours[0].EmployeeId); + } +} diff --git a/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetTaskEntriesQuery.cs b/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetTaskEntriesQuery.cs index 3d00e9e..4da7993 100644 --- a/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetTaskEntriesQuery.cs +++ b/Application/Features/Internal/GetEmployeesTrackedTaskHours/GetTaskEntriesQuery.cs @@ -4,7 +4,16 @@ namespace Application.Features.Internal.GetEmployeesTrackedTaskHours; -public class GetTaskEntriesQuery +public interface IGetTaskEntriesQuery +{ + Task> GetAsync( + long projectId, + DateOnly startDate, + DateOnly endDate + ); +} + +public class GetTaskEntriesQuery : IGetTaskEntriesQuery { private readonly TenantAppDbContext _context; diff --git a/Application/Features/Reporting/GetPersonalReport/GetPersonalReportHandler.cs b/Application/Features/Reporting/GetPersonalReport/GetPersonalReportHandler.cs index 1bc6785..98bbef4 100644 --- a/Application/Features/Reporting/GetPersonalReport/GetPersonalReportHandler.cs +++ b/Application/Features/Reporting/GetPersonalReport/GetPersonalReportHandler.cs @@ -46,7 +46,7 @@ int month StartTime = x.StartTime, EndTime = x.EndTime, Hours = x.GetDurationInHours(), - TrackedHoursPerDay = TotalTrackedMinutesPerDayCalculator.Calculate(employeeTrackedEntries, x.StartTime) / 60, + TrackedHoursPerDay = TotalTrackedMinutesPerDayCalculator.Calculate(employeeTrackedEntries, x.StartTime).ToHoursWithoutRounding(), EntryType = x.Type, Project = new ProjectDto { @@ -71,7 +71,7 @@ int month StartTime = x.StartTime, EndTime = x.EndTime, Hours = x.GetDurationInHours(), - TrackedHoursPerDay = TotalTrackedMinutesPerDayCalculator.Calculate(employeeTrackedEntries, x.StartTime) / 60, + TrackedHoursPerDay = TotalTrackedMinutesPerDayCalculator.Calculate(employeeTrackedEntries, x.StartTime).ToHoursWithoutRounding(), EntryType = x.Type, Project = null!, Task = null!, @@ -95,8 +95,8 @@ int month return new GetPersonalReportResponse { TrackedEntries = sortedByDateAllEntries, - TaskHours = taskTotalMinutes / 60, - UnwellHours = unwellTotalMinutes / 60 + TaskHours = taskTotalMinutes.ToHoursWithoutRounding(), + UnwellHours = unwellTotalMinutes.ToHoursWithoutRounding() }; } } diff --git a/Core/Entities/TrackedEntryBase.cs b/Core/Entities/TrackedEntryBase.cs index 5ba69e4..6a1492a 100644 --- a/Core/Entities/TrackedEntryBase.cs +++ b/Core/Entities/TrackedEntryBase.cs @@ -33,6 +33,6 @@ public int GetDurationInMinutes() public decimal GetDurationInHours() { - return (decimal)GetDurationInMinutes() / 60; + return GetDurationInMinutes() / 60m; } } diff --git a/README.md b/README.md index 5740446..afe3044 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # inner-circle-time-api -[![coverage](https://img.shields.io/badge/e2e_coverage-27.97%25-crimson)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) -[![coverage](https://img.shields.io/badge/units_coverage-22.69%25-crimson)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) -[![coverage](https://img.shields.io/badge/integration_coverage-60.48%25-orange)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) -[![coverage](https://img.shields.io/badge/full_coverage-93.86%25-forestgreen)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/e2e_coverage-27.66%25-crimson)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/units_coverage-24.71%25-crimson)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/integration_coverage-59.60%25-crimson)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/full_coverage-93.95%25-forestgreen)](https://github.com/TourmalineCore/inner-circle-time-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) This repo contains Inner Circle Time API.