diff --git a/CheckDrive.Api/CheckDrive.Api/CheckDrive.Api.csproj b/CheckDrive.Api/CheckDrive.Api/CheckDrive.Api.csproj
index 0a76477a..9ac5645d 100644
--- a/CheckDrive.Api/CheckDrive.Api/CheckDrive.Api.csproj
+++ b/CheckDrive.Api/CheckDrive.Api/CheckDrive.Api.csproj
@@ -24,6 +24,7 @@
+
@@ -33,4 +34,8 @@
+
+
+
+
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/AccountsController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/AccountsController.cs
index 2b92117f..13a7a01c 100644
--- a/CheckDrive.Api/CheckDrive.Api/Controllers/AccountsController.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/AccountsController.cs
@@ -1,6 +1,7 @@
using CheckDrive.Application.Constants;
using CheckDrive.Application.DTOs.Account;
using CheckDrive.Application.Interfaces;
+using CheckDrive.Domain.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -18,9 +19,9 @@ public AccountsController(IAccountService service)
}
[HttpGet]
- public async Task>> GetAsync()
+ public async Task>> GetAsync(EmployeePosition? position)
{
- var accounts = await _service.GetAsync();
+ var accounts = await _service.GetAsync(position);
return Ok(accounts);
}
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/Auth/AuthController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/AuthController.cs
similarity index 93%
rename from CheckDrive.Api/CheckDrive.Api/Controllers/Auth/AuthController.cs
rename to CheckDrive.Api/CheckDrive.Api/Controllers/AuthController.cs
index 7e4e1e16..2659d1e6 100644
--- a/CheckDrive.Api/CheckDrive.Api/Controllers/Auth/AuthController.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/AuthController.cs
@@ -2,7 +2,7 @@
using CheckDrive.Application.Interfaces.Auth;
using Microsoft.AspNetCore.Mvc;
-namespace CheckDrive.Api.Controllers.Auth;
+namespace CheckDrive.Api.Controllers;
[Route("api/auth")]
[ApiController]
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/CarsController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/CarsController.cs
new file mode 100644
index 00000000..62159875
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/CarsController.cs
@@ -0,0 +1,63 @@
+using CheckDrive.Application.DTOs.Car;
+using CheckDrive.Application.Interfaces;
+using CheckDrive.Application.QueryParameters;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CheckDrive.Api.Controllers;
+
+[Route("api/cars")]
+[ApiController]
+public class CarsController : ControllerBase
+{
+ private readonly ICarService _carService;
+
+ public CarsController(ICarService carService)
+ {
+ _carService = carService;
+ }
+
+ [HttpGet]
+ public async Task>> GetAllAsync(CarQueryParameters queryParameters)
+ {
+ var cars = await _carService.GetAllAsync(queryParameters);
+
+ return Ok(cars);
+ }
+
+ [HttpGet("{id:int}", Name = "GetCarByIdAsync")]
+ public async Task> GetByIdAsync(int id)
+ {
+ var car = await _carService.GetByIdAsync(id);
+
+ return Ok(car);
+ }
+
+ [HttpPost]
+ public async Task> CreateAsync(CreateCarDto car)
+ {
+ var createdCar = await _carService.CreateAsync(car);
+
+ return CreatedAtAction("GetCarByIdAsync", createdCar, new { id = createdCar.Id });
+ }
+
+ [HttpPut("{id:int}")]
+ public async Task> UpdateAsync(int id, UpdateCarDto car)
+ {
+ if (id != car.Id)
+ {
+ return BadRequest($"Route parameter id: {id} does not match with body parameter id: {car.Id}.");
+ }
+
+ var updatedCar = await _carService.UpdateAsync(car);
+
+ return Ok(updatedCar);
+ }
+
+ [HttpDelete("{id:int}")]
+ public async Task DeleteAsync(int id)
+ {
+ await _carService.DeleteAsync(id);
+
+ return NoContent();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/CheckPointsController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/CheckPointsController.cs
index 97448bd6..7ed0264b 100644
--- a/CheckDrive.Api/CheckDrive.Api/Controllers/CheckPointsController.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/CheckPointsController.cs
@@ -23,4 +23,20 @@ public async Task>> GetCheckPointsAsync([FromQu
return Ok(checkPoints);
}
+
+ [HttpGet("drivers/{driverId:int}/current")]
+ public async Task> GetCurrentCheckPointByDriverIdAsync(int driverId)
+ {
+ var checkPoint = await _service.GetCurrentCheckPointByDriverIdAsync(driverId);
+
+ return Ok(checkPoint);
+ }
+
+ [HttpPut("{id:int}/cancel")]
+ public async Task> CancelCheckPoint(int id)
+ {
+ await _service.CancelCheckPointAsync(id);
+
+ return NoContent();
+ }
}
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/DriversController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/DriversController.cs
new file mode 100644
index 00000000..ff3a3460
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/DriversController.cs
@@ -0,0 +1,33 @@
+using CheckDrive.Application.DTOs.Driver;
+using CheckDrive.Application.Interfaces;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CheckDrive.Api.Controllers;
+
+[Route("api/drivers")]
+[ApiController]
+public class DriversController : ControllerBase
+{
+ private readonly IDriverService _driverService;
+
+ public DriversController(IDriverService driverService)
+ {
+ _driverService = driverService ?? throw new ArgumentNullException(nameof(driverService));
+ }
+
+ [HttpGet]
+ public async Task>> GetAvailableDriversAsync()
+ {
+ var drivers = await _driverService.GetAvailableDriversAsync();
+
+ return Ok(drivers);
+ }
+
+ [HttpPost("reviews")]
+ public async Task CreateReviewConfirmation(DriverReviewConfirmationDto confirmationDto)
+ {
+ await _driverService.CreateReviewConfirmation(confirmationDto);
+
+ return NoContent();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/OilMarksController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/OilMarksController.cs
new file mode 100644
index 00000000..53c37b78
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/OilMarksController.cs
@@ -0,0 +1,63 @@
+using CheckDrive.Application.DTOs.OilMark;
+using CheckDrive.Application.Interfaces;
+using CheckDrive.Application.QueryParameters;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CheckDrive.Api.Controllers;
+
+[Route("api/oilMarks")]
+[ApiController]
+public class OilMarksController : ControllerBase
+{
+ private readonly IOilMarkService _oilMarkService;
+
+ public OilMarksController(IOilMarkService oilMarkService)
+ {
+ _oilMarkService = oilMarkService ?? throw new ArgumentNullException(nameof(oilMarkService));
+ }
+
+ [HttpGet]
+ public async Task>> GetAsync(OilMarkQueryParameters queryParameters)
+ {
+ var oilMarks = await _oilMarkService.GetAllAsync(queryParameters);
+
+ return Ok(oilMarks);
+ }
+
+ [HttpGet("{id:int}", Name = "GetOilMarkById")]
+ public async Task> GetByIdAsync(int id)
+ {
+ var oilMark = await _oilMarkService.GetByIdAsync(id);
+
+ return oilMark;
+ }
+
+ [HttpPost]
+ public async Task> CreateAsync(CreateOilMarkDto oilMark)
+ {
+ var createdOilMark = await _oilMarkService.CreateAsync(oilMark);
+
+ return CreatedAtAction("GetOilMarkById", oilMark, new { id = createdOilMark.Id });
+ }
+
+ [HttpPut("{id:int}")]
+ public async Task> UpdateAsync(int id, UpdateOilMarkDto oilMark)
+ {
+ if (id != oilMark.Id)
+ {
+ return BadRequest($"Route parameter id: {id} does not match with body parameter id: {oilMark.Id}.");
+ }
+
+ var updatedOilMark = await _oilMarkService.UpdateAsync(oilMark);
+
+ return Ok(updatedOilMark);
+ }
+
+ [HttpDelete("{id:int}")]
+ public async Task DeleteAsync(int id)
+ {
+ await _oilMarkService.DeleteAsync(id);
+
+ return NoContent();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/DispatcherReviewsController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/DispatcherReviewsController.cs
index 11955e3d..a7b7abb1 100644
--- a/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/DispatcherReviewsController.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/DispatcherReviewsController.cs
@@ -4,7 +4,7 @@
namespace CheckDrive.Api.Controllers.Reviews;
-[Route("api/reviews/dispatcher/{dispatcherId:int}")]
+[Route("api/reviews/dispatchers/{dispatcherId:int}")]
[ApiController]
public class DispatcherReviewsController : ControllerBase
{
diff --git a/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/ReviewHistoriesController.cs b/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/ReviewHistoriesController.cs
new file mode 100644
index 00000000..f8d66138
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Api/Controllers/Reviews/ReviewHistoriesController.cs
@@ -0,0 +1,52 @@
+using Microsoft.AspNetCore.Mvc;
+using CheckDrive.Application.DTOs.CheckPoint;
+using CheckDrive.Application.DTOs.DoctorReview;
+using CheckDrive.Application.DTOs.OperatorReview;
+using CheckDrive.Application.DTOs.Review;
+using CheckDrive.Application.Interfaces.Review;
+
+namespace CheckDrive.Api.Controllers.Reviews;
+
+[Route("api/reviews/histories")]
+[ApiController]
+public class ReviewHistoriesController : ControllerBase
+{
+ private readonly IReviewHistoryService _historyService;
+
+ public ReviewHistoriesController(IReviewHistoryService historyService)
+ {
+ _historyService = historyService ?? throw new ArgumentNullException(nameof(historyService));
+ }
+
+ [HttpGet("drivers/{driverId:int}")]
+ public async Task>> GetDriverHistoriesAsync(int driverId)
+ {
+ var reviews = await _historyService.GetDriverHistoriesAsync(driverId);
+
+ return Ok(reviews);
+ }
+
+ [HttpGet("doctors/{doctorId:int}")]
+ public async Task>> GetDoctorHistoriesAsync(int doctorId)
+ {
+ var reviews = await _historyService.GetDoctorHistoriesAsync(doctorId);
+
+ return Ok(reviews);
+ }
+
+ [HttpGet("mechanics/{mechanicId:int}")]
+ public async Task>> GetMechanicHistoriesAsync(int mechanicId)
+ {
+ var reviews = await _historyService.GetMechanicHistoriesAsync(mechanicId);
+
+ return Ok(reviews);
+ }
+
+ [HttpGet("operators/{operatorId:int}")]
+ public async Task>> GetOperatorHistoriesAsync(int operatorId)
+ {
+ var reviews = await _historyService.GetOperatorHistoriesAsync(operatorId);
+
+ return Ok(reviews);
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Api/Extensions/DependencyInjection.cs b/CheckDrive.Api/CheckDrive.Api/Extensions/DependencyInjection.cs
index 13f20e57..ff3eb848 100644
--- a/CheckDrive.Api/CheckDrive.Api/Extensions/DependencyInjection.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Extensions/DependencyInjection.cs
@@ -9,7 +9,6 @@
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Text;
-using CheckDrive.Api.Filters;
namespace CheckDrive.Api.Extensions;
@@ -21,6 +20,10 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi
services.RegisterInfrastructure(configuration);
services.AddSingleton();
+ services.AddSignalR(options =>
+ {
+ options.EnableDetailedErrors = true;
+ });
AddControllers(services);
AddSwagger(services);
@@ -56,15 +59,19 @@ private static void AddSwagger(IServiceCollection services)
.AddSwaggerGen(setup =>
{
setup.SwaggerDoc("v1", new OpenApiInfo { Title = "Check-Drive API", Version = "v1" });
-
- setup.SchemaFilter();
- });
+ })
+ .AddSwaggerGenNewtonsoftSupport();
}
private static void AddAuthentication(IServiceCollection services, IConfiguration configuration)
{
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get();
+ if (jwtOptions is null)
+ {
+ throw new InvalidOperationException("Could not load JWT configurations.");
+ }
+
services
.AddAuthentication(options =>
{
@@ -84,7 +91,7 @@ private static void AddAuthentication(IServiceCollection services, IConfiguratio
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
- Encoding.UTF8.GetBytes(jwtOptions!.SecretKey))
+ Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
};
options.Events = new JwtBearerEvents
diff --git a/CheckDrive.Api/CheckDrive.Api/Extensions/StartupExtensions.cs b/CheckDrive.Api/CheckDrive.Api/Extensions/StartupExtensions.cs
index fe0eadbd..65cd1b56 100644
--- a/CheckDrive.Api/CheckDrive.Api/Extensions/StartupExtensions.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Extensions/StartupExtensions.cs
@@ -1,6 +1,5 @@
using CheckDrive.Api.Helpers;
using CheckDrive.Api.Middlewares;
-using CheckDrive.Application.Constants;
using CheckDrive.Domain.Interfaces;
using CheckDrive.TestDataCreator.Configurations;
using Microsoft.AspNetCore.Identity;
@@ -28,4 +27,7 @@ public static IApplicationBuilder UseDatabaseSeeder(this WebApplication app)
return app;
}
+
+ public static bool IsTesting(this IHostEnvironment environment)
+ => environment.IsEnvironment("Testing");
}
diff --git a/CheckDrive.Api/CheckDrive.Api/Filters/EnumSchemaFilter.cs b/CheckDrive.Api/CheckDrive.Api/Filters/EnumSchemaFilter.cs
deleted file mode 100644
index 3c1b41f2..00000000
--- a/CheckDrive.Api/CheckDrive.Api/Filters/EnumSchemaFilter.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
-using Swashbuckle.AspNetCore.SwaggerGen;
-
-namespace CheckDrive.Api.Filters;
-
-public class EnumSchemaFilter : ISchemaFilter
-{
- public void Apply(OpenApiSchema schema, SchemaFilterContext context)
- {
- if (context.Type.IsEnum)
- {
- // Enum.GetNames returns the names of the enum values
- var enumNames = System.Enum.GetNames(context.Type);
-
- schema.Enum.Clear();
- foreach (var name in enumNames)
- {
- schema.Enum.Add(new OpenApiString(name));
- }
- }
- }
-}
diff --git a/CheckDrive.Api/CheckDrive.Api/Helpers/DatabaseSeeder.cs b/CheckDrive.Api/CheckDrive.Api/Helpers/DatabaseSeeder.cs
index 788e8267..25a79a98 100644
--- a/CheckDrive.Api/CheckDrive.Api/Helpers/DatabaseSeeder.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Helpers/DatabaseSeeder.cs
@@ -1,5 +1,4 @@
-using Bogus;
-using CheckDrive.Domain.Entities;
+using CheckDrive.Domain.Entities;
using CheckDrive.Domain.Interfaces;
using CheckDrive.TestDataCreator;
using CheckDrive.TestDataCreator.Configurations;
@@ -9,8 +8,6 @@ namespace CheckDrive.Api.Helpers;
public static class DatabaseSeeder
{
- private static Faker _faker = new Faker();
-
public static void SeedDatabase(
ICheckDriveDbContext context,
UserManager userManager,
@@ -23,6 +20,7 @@ public static void SeedDatabase(
CreateMechanics(context, userManager, options);
CreateOperators(context, userManager, options);
CreateDispatchers(context, userManager, options);
+ CreateManagers(context, userManager, options);
}
private static void CreateCars(ICheckDriveDbContext context, DataSeedOptions options)
@@ -144,7 +142,7 @@ private static void CreateMechanics(ICheckDriveDbContext context, UserManager x.Name == "mechanic");
var uniqueMechanicsByName = new Dictionary();
- for (int i = 0; i < options.DriversCount; i++)
+ for (int i = 0; i < options.MechanicsCount; i++)
{
var account = FakeDataGenerator.GetAccount().Generate();
var mechanic = FakeDataGenerator.GetEmployee().Generate();
@@ -182,7 +180,7 @@ private static void CreateOperators(ICheckDriveDbContext context, UserManager x.Name == "operator");
var uniqueOperatorsByName = new Dictionary();
- for (int i = 0; i < options.DriversCount; i++)
+ for (int i = 0; i < options.OperatorsCount; i++)
{
var account = FakeDataGenerator.GetAccount().Generate();
var @operator = FakeDataGenerator.GetEmployee().Generate();
@@ -220,7 +218,7 @@ private static void CreateDispatchers(ICheckDriveDbContext context, UserManager<
var role = context.Roles.First(x => x.Name == "dispatcher");
var uniqueDispatchersByName = new Dictionary();
- for (int i = 0; i < options.DriversCount; i++)
+ for (int i = 0; i < options.DispatchersCount; i++)
{
var account = FakeDataGenerator.GetAccount().Generate();
var dispatcher = FakeDataGenerator.GetEmployee().Generate();
@@ -251,6 +249,44 @@ private static void CreateDispatchers(ICheckDriveDbContext context, UserManager<
context.SaveChanges();
}
+ private static void CreateManagers(ICheckDriveDbContext context, UserManager userManager, DataSeedOptions options)
+ {
+ if (context.Managers.Any()) return;
+
+ var role = context.Roles.First(x => x.Name == "manager");
+ var uniqueManagersByName = new Dictionary();
+
+ for (int i = 0; i < options.ManagersCount; i++)
+ {
+ var account = FakeDataGenerator.GetAccount().Generate();
+ var manager = FakeDataGenerator.GetEmployee().Generate();
+
+ if (uniqueManagersByName.TryAdd(manager.FirstName + manager.LastName, manager))
+ {
+ var result = userManager.CreateAsync(account, $"Qwerty-{i}");
+
+ if (!result.Result.Succeeded)
+ {
+ continue;
+ }
+
+ manager.Account = account;
+ context.Managers.Add(manager);
+ }
+ }
+
+ context.SaveChanges();
+ var managers = context.Managers.ToArray();
+
+ foreach (var manager in managers)
+ {
+ var userRole = new IdentityUserRole { RoleId = role.Id, UserId = manager.AccountId };
+ context.UserRoles.Add(userRole);
+ }
+
+ context.SaveChanges();
+ }
+
//private static void CreateEmployees(ICheckDriveDbContext context, UserManager userManager, DataSeedOptions options)
//{
// if (context.Users.Any())
diff --git a/CheckDrive.Api/CheckDrive.Api/Middlewares/ErrorHandlerMiddleware.cs b/CheckDrive.Api/CheckDrive.Api/Middlewares/ErrorHandlerMiddleware.cs
index f5a98a32..c7daaa2a 100644
--- a/CheckDrive.Api/CheckDrive.Api/Middlewares/ErrorHandlerMiddleware.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Middlewares/ErrorHandlerMiddleware.cs
@@ -32,7 +32,7 @@ private async Task HandleAsync(HttpContext context, Exception exception)
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
string message = "Internal server error. Something went wrong, please try again later.";
- if (exception is NotFound)
+ if (exception is EntityNotFoundException)
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
message = exception.Message;
diff --git a/CheckDrive.Api/CheckDrive.Api/Program.cs b/CheckDrive.Api/CheckDrive.Api/Program.cs
index 2bed574e..71969fd1 100644
--- a/CheckDrive.Api/CheckDrive.Api/Program.cs
+++ b/CheckDrive.Api/CheckDrive.Api/Program.cs
@@ -1,10 +1,11 @@
using CheckDrive.Api.Extensions;
using CheckDrive.Api.Helpers;
+using CheckDrive.Application.Hubs;
using Microsoft.AspNetCore.CookiePolicy;
using Serilog;
Log.Logger = new LoggerConfiguration()
- .MinimumLevel.Verbose()
+ .MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console(new CustomJsonFormatter())
.WriteTo.File(new CustomJsonFormatter(), "logs/logs.txt", rollingInterval: RollingInterval.Day)
@@ -19,7 +20,7 @@
var app = builder.Build();
-if (!app.Environment.IsProduction())
+if (app.Environment.IsTesting())
{
app.UseDatabaseSeeder();
}
@@ -44,6 +45,8 @@
app.MapControllers();
+app.MapHub("/review-hub");
+
app.Run();
// For API testing
diff --git a/CheckDrive.Api/CheckDrive.Api/appsettings.Testing.json b/CheckDrive.Api/CheckDrive.Api/appsettings.Testing.json
index adc64dd0..31fe450b 100644
--- a/CheckDrive.Api/CheckDrive.Api/appsettings.Testing.json
+++ b/CheckDrive.Api/CheckDrive.Api/appsettings.Testing.json
@@ -26,6 +26,7 @@
"DoctorsCount": 5,
"MechanicsCount": 10,
"OperatorsCount": 10,
- "DispatchersCount": 10
+ "DispatchersCount": 10,
+ "ManagersCount": 2
}
}
\ No newline at end of file
diff --git a/CheckDrive.Api/CheckDrive.Application/BackgroundJobs/CarMileageResetService.cs b/CheckDrive.Api/CheckDrive.Application/BackgroundJobs/CarMileageResetService.cs
new file mode 100644
index 00000000..14ea295a
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/BackgroundJobs/CarMileageResetService.cs
@@ -0,0 +1,66 @@
+using CheckDrive.Domain.Interfaces;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace CheckDrive.Application.BackgroundJobs;
+
+internal sealed class CarMileageResetService : BackgroundService
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ public CarMileageResetService(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ DateTime now = DateTime.UtcNow;
+
+ // Check if it's the first day of the month at midnight UTC
+ if (now.Day == 1 && now.Hour == 0)
+ {
+ await ResetCarMonthlyMileage(stoppingToken);
+
+ // Check if it's the first month of the year
+ if (now.Month == 1)
+ {
+ await ResetCarYearlyMileage(stoppingToken);
+ }
+ }
+
+ await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
+ }
+ }
+
+ private async Task ResetCarMonthlyMileage(CancellationToken stoppingToken)
+ {
+ using var scope = _serviceProvider.CreateScope();
+ var context = scope.ServiceProvider.GetRequiredService();
+
+ await context.Cars.ExecuteUpdateAsync(
+ x => x.SetProperty(x => x.CurrentMonthMileage, 0),
+ stoppingToken);
+
+ await context.Cars.ExecuteUpdateAsync(
+ x => x.SetProperty(x => x.CurrentMonthFuelConsumption, 0),
+ stoppingToken);
+ }
+
+ private async Task ResetCarYearlyMileage(CancellationToken stoppingToken)
+ {
+ using var scope = _serviceProvider.CreateScope();
+ var context = scope.ServiceProvider.GetRequiredService();
+
+ await context.Cars.ExecuteUpdateAsync(
+ x => x.SetProperty(x => x.CurrentYearMileage, 0),
+ stoppingToken);
+
+ await context.Cars.ExecuteUpdateAsync(
+ x => x.SetProperty(x => x.CurrentYearFuelConsumption, 0),
+ stoppingToken);
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Account/AccountDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Account/AccountDto.cs
index a5f907bf..5f0cbfaf 100644
--- a/CheckDrive.Api/CheckDrive.Application/DTOs/Account/AccountDto.cs
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Account/AccountDto.cs
@@ -4,6 +4,7 @@ namespace CheckDrive.Application.DTOs.Account;
public record class AccountDto(
string Id,
+ string AccountId,
string Username,
string PhoneNumber,
string? Email,
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Car/CarDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Car/CarDto.cs
new file mode 100644
index 00000000..0ff6f67b
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Car/CarDto.cs
@@ -0,0 +1,23 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Car;
+
+public sealed record CarDto(
+ int Id,
+ string Model,
+ string Color,
+ string Number,
+ int ManufacturedYear,
+ int Mileage,
+ int CurrentMonthMileage,
+ int CurrentYearMileage,
+ int MonthlyDistanceLimit,
+ int YearlyDistanceLimit,
+ decimal CurrentMonthFuelConsumption,
+ decimal CurrentYearFuelConsumption,
+ decimal MonthlyFuelConsumptionLimit,
+ decimal YearlyFuelConsumptionLimit,
+ decimal AverageFuelConsumption,
+ decimal FuelCapacity,
+ decimal RemainingFuel,
+ CarStatus Status);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Car/CreateCarDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Car/CreateCarDto.cs
new file mode 100644
index 00000000..41e70263
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Car/CreateCarDto.cs
@@ -0,0 +1,22 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Car;
+
+public record CreateCarDto(
+ string Model,
+ string Color,
+ string Number,
+ int ManufacturedYear,
+ int Mileage,
+ int CurrentMonthMileage,
+ int CurrentYearMileage,
+ int MonthlyDistanceLimit,
+ int YearlyDistanceLimit,
+ decimal CurrentMonthFuelConsumption,
+ decimal CurrentYearFuelConsumption,
+ decimal MonthlyFuelConsumptionLimit,
+ decimal YearlyFuelConsumptionLimit,
+ decimal AverageFuelConsumption,
+ decimal FuelCapacity,
+ decimal RemainingFuel,
+ CarStatus Status);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Car/UpdateCarDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Car/UpdateCarDto.cs
new file mode 100644
index 00000000..b7fb2deb
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Car/UpdateCarDto.cs
@@ -0,0 +1,23 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Car;
+
+public record UpdateCarDto(
+ int Id,
+ string Model,
+ string Color,
+ string Number,
+ int ManufacturedYear,
+ int Mileage,
+ int CurrentMonthMileage,
+ int CurrentYearMileage,
+ int MonthlyDistanceLimit,
+ int YearlyDistanceLimit,
+ decimal CurrentMonthFuelConsumption,
+ decimal CurrentYearFuelConsumption,
+ decimal MonthlyFuelConsumptionLimit,
+ decimal YearlyFuelConsumptionLimit,
+ decimal AverageFuelConsumption,
+ decimal FuelCapacity,
+ decimal RemainingFuel,
+ CarStatus Status);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/CheckPoint/DriverCheckPointDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/CheckPoint/DriverCheckPointDto.cs
new file mode 100644
index 00000000..508cd3b4
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/CheckPoint/DriverCheckPointDto.cs
@@ -0,0 +1,14 @@
+using CheckDrive.Application.DTOs.Car;
+using CheckDrive.Application.DTOs.Review;
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.CheckPoint;
+
+public sealed record DriverCheckPointDto(
+ int Id,
+ DateTime StartDate,
+ CheckPointStage Stage,
+ CheckPointStatus Status,
+ string DriverName,
+ CarDto? Car,
+ List Reviews);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/CreateDispatcherReviewDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/CreateDispatcherReviewDto.cs
index 2420f0b7..b3ef43ab 100644
--- a/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/CreateDispatcherReviewDto.cs
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/CreateDispatcherReviewDto.cs
@@ -8,7 +8,7 @@ public sealed record CreateDispatcherReviewDto(
string? Notes,
bool IsApprovedByReviewer,
decimal? FuelConsumptionAdjustment,
- int? DistanceTravelledAdjustment)
+ int? FinalMileageAdjustment)
: CreateReviewDtoBase(
ReviewerId: ReviewerId,
Notes: Notes,
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/DispatcherReviewDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/DispatcherReviewDto.cs
index 34e2c377..9459d8ba 100644
--- a/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/DispatcherReviewDto.cs
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/DispatcherReview/DispatcherReviewDto.cs
@@ -23,4 +23,4 @@ public sealed record DispatcherReviewDto(
Notes: Notes,
Date: Date,
Status: Status,
- Type: ReviewType.Dispatcher);
+ Type: ReviewType.DispatcherReview);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/DoctorReview/DoctorReviewDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/DoctorReview/DoctorReviewDto.cs
index db025f00..5d67089d 100644
--- a/CheckDrive.Api/CheckDrive.Application/DTOs/DoctorReview/DoctorReviewDto.cs
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/DoctorReview/DoctorReviewDto.cs
@@ -21,4 +21,4 @@ public sealed record DoctorReviewDto(
Date: Date,
Notes: Notes,
Status: Status,
- Type: ReviewType.Doctor);
+ Type: ReviewType.DoctorReview);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Driver/DriverDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Driver/DriverDto.cs
new file mode 100644
index 00000000..7027e466
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Driver/DriverDto.cs
@@ -0,0 +1,6 @@
+namespace CheckDrive.Application.DTOs.Driver;
+
+public record DriverDto(
+ int Id,
+ string AccountId,
+ string FullName);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Driver/DriverReviewConfirmationDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Driver/DriverReviewConfirmationDto.cs
new file mode 100644
index 00000000..c72efd9f
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Driver/DriverReviewConfirmationDto.cs
@@ -0,0 +1,9 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Driver;
+
+public sealed record DriverReviewConfirmationDto(
+ int CheckPointId,
+ ReviewType ReviewType,
+ bool IsAcceptedByDriver,
+ string? Notes);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/MechanicHandover/MechanicHandoverReviewDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/MechanicHandover/MechanicHandoverReviewDto.cs
index 8f89df4b..c2d362f5 100644
--- a/CheckDrive.Api/CheckDrive.Application/DTOs/MechanicHandover/MechanicHandoverReviewDto.cs
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/MechanicHandover/MechanicHandoverReviewDto.cs
@@ -1,4 +1,5 @@
-using CheckDrive.Application.DTOs.Review;
+using CheckDrive.Application.DTOs.Car;
+using CheckDrive.Application.DTOs.Review;
using CheckDrive.Domain.Enums;
namespace CheckDrive.Application.DTOs.MechanicHandover;
@@ -12,7 +13,8 @@ public sealed record MechanicHandoverReviewDto(
string? Notes,
DateTime Date,
ReviewStatus Status,
- int InitialMileage)
+ int InitialMileage,
+ CarDto Car)
: ReviewDtoBase(
CheckPointId: CheckPointId,
ReviewerId: ReviewerId,
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/CreateOilMarkDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/CreateOilMarkDto.cs
new file mode 100644
index 00000000..7d6d7de0
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/CreateOilMarkDto.cs
@@ -0,0 +1,3 @@
+namespace CheckDrive.Application.DTOs.OilMark;
+
+public sealed record CreateOilMarkDto(string Name);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/OilMarkDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/OilMarkDto.cs
new file mode 100644
index 00000000..220c42fd
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/OilMarkDto.cs
@@ -0,0 +1,5 @@
+namespace CheckDrive.Application.DTOs.OilMark;
+
+public sealed record OilMarkDto(
+ int Id,
+ string Name);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/UpdateOilMarkDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/UpdateOilMarkDto.cs
new file mode 100644
index 00000000..d6b15722
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/OilMark/UpdateOilMarkDto.cs
@@ -0,0 +1,3 @@
+namespace CheckDrive.Application.DTOs.OilMark;
+
+public sealed record UpdateOilMarkDto(int Id, string Name);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/OperatorReview/OperatorReviewDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/OperatorReview/OperatorReviewDto.cs
index 65a33968..b7643133 100644
--- a/CheckDrive.Api/CheckDrive.Application/DTOs/OperatorReview/OperatorReviewDto.cs
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/OperatorReview/OperatorReviewDto.cs
@@ -25,4 +25,4 @@ public sealed record OperatorReviewDto(
Notes: Notes,
Date: Date,
Status: Status,
- Type: ReviewType.Operator);
+ Type: ReviewType.OperatorReview);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Review/DriverReviewDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Review/DriverReviewDto.cs
new file mode 100644
index 00000000..c121b63e
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Review/DriverReviewDto.cs
@@ -0,0 +1,10 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Review;
+
+public sealed record DriverReviewDto(
+ string? Notes,
+ string ReviewerName,
+ DateTime Date,
+ ReviewType Type,
+ ReviewStatus Status);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Review/MechanicReviewHistoryDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Review/MechanicReviewHistoryDto.cs
new file mode 100644
index 00000000..37067215
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Review/MechanicReviewHistoryDto.cs
@@ -0,0 +1,11 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Review;
+
+public sealed record MechanicReviewHistoryDto(
+ int ReviewId,
+ int DriverId,
+ string DriverName,
+ string? Notes,
+ DateTime Date,
+ ReviewStatus Status);
diff --git a/CheckDrive.Api/CheckDrive.Application/DTOs/Review/ReviewConfirmationDto.cs b/CheckDrive.Api/CheckDrive.Application/DTOs/Review/ReviewConfirmationDto.cs
new file mode 100644
index 00000000..76b444d6
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/DTOs/Review/ReviewConfirmationDto.cs
@@ -0,0 +1,8 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.DTOs.Review;
+
+public sealed record ReviewConfirmationDto(
+ int CheckPointId,
+ ReviewType ReviewType,
+ string Message);
diff --git a/CheckDrive.Api/CheckDrive.Application/Extensions/DependencyInjection.cs b/CheckDrive.Api/CheckDrive.Application/Extensions/DependencyInjection.cs
index 6370de63..ce463ed5 100644
--- a/CheckDrive.Api/CheckDrive.Application/Extensions/DependencyInjection.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Extensions/DependencyInjection.cs
@@ -1,10 +1,14 @@
-using CheckDrive.Application.Interfaces.Review;
+using FluentValidation;
+using Microsoft.Extensions.DependencyInjection;
+using CheckDrive.Application.Interfaces.Review;
using CheckDrive.Application.Interfaces;
using CheckDrive.Application.Services;
using CheckDrive.Application.Services.Review;
-using Microsoft.Extensions.DependencyInjection;
using CheckDrive.Application.Interfaces.Auth;
using CheckDrive.Application.Services.Auth;
+using CheckDrive.Application.BackgroundJobs;
+using CheckDrive.Application.Validators.Car;
+using CheckDrive.Application.Mappings;
namespace CheckDrive.Application.Extensions;
@@ -12,8 +16,10 @@ public static class DependencyInjection
{
public static IServiceCollection RegisterApplication(this IServiceCollection services)
{
+ services.AddAutoMapper(typeof(CarMappings).Assembly);
+ services.AddValidatorsFromAssemblyContaining();
+
AddServices(services);
- services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
return services;
}
@@ -28,5 +34,11 @@ private static void AddServices(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ services.AddHostedService();
}
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Hubs/IReviewHub.cs b/CheckDrive.Api/CheckDrive.Application/Hubs/IReviewHub.cs
new file mode 100644
index 00000000..8c3bd3a2
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Hubs/IReviewHub.cs
@@ -0,0 +1,14 @@
+using CheckDrive.Application.DTOs.MechanicAcceptance;
+using CheckDrive.Application.DTOs.MechanicHandover;
+using CheckDrive.Application.DTOs.OperatorReview;
+using CheckDrive.Application.DTOs.Review;
+
+namespace CheckDrive.Application.Hubs;
+
+public interface IReviewHub
+{
+ Task NotifyDoctorReview(ReviewDtoBase review);
+ Task MechanicHandoverConfirmation(MechanicHandoverReviewDto review);
+ Task OperatorReviewConfirmation(OperatorReviewDto review);
+ Task MechanicAcceptanceConfirmation(MechanicAcceptanceReviewDto review);
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Hubs/ReviewHub.cs b/CheckDrive.Api/CheckDrive.Application/Hubs/ReviewHub.cs
new file mode 100644
index 00000000..f6136d58
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Hubs/ReviewHub.cs
@@ -0,0 +1,14 @@
+using CheckDrive.Application.DTOs.Review;
+using Microsoft.AspNetCore.SignalR;
+
+namespace CheckDrive.Application.Hubs;
+
+public sealed class ReviewHub : Hub
+{
+ public async Task NotifyNewReviewAsync(ReviewDtoBase review)
+ {
+ await Clients
+ .User(review.DriverId.ToString())
+ .NotifyDoctorReview(review);
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/Auth/IJwtTokenGenerator.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/Auth/IJwtTokenGenerator.cs
index a801492f..c4541c2f 100644
--- a/CheckDrive.Api/CheckDrive.Application/Interfaces/Auth/IJwtTokenGenerator.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/Auth/IJwtTokenGenerator.cs
@@ -1,8 +1,8 @@
-using Microsoft.AspNetCore.Identity;
+using CheckDrive.Domain.Entities;
namespace CheckDrive.Application.Interfaces.Auth;
public interface IJwtTokenGenerator
{
- string GenerateToken(IdentityUser user, IList roles);
+ string GenerateToken(Employee employee, IList roles);
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/IAccountService.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/IAccountService.cs
index dc763b81..54a08d63 100644
--- a/CheckDrive.Api/CheckDrive.Application/Interfaces/IAccountService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/IAccountService.cs
@@ -1,10 +1,11 @@
using CheckDrive.Application.DTOs.Account;
+using CheckDrive.Domain.Enums;
namespace CheckDrive.Application.Interfaces;
public interface IAccountService
{
- Task> GetAsync();
+ Task> GetAsync(EmployeePosition? position);
Task GetByIdAsync(string id);
Task CreateAsync(CreateAccountDto account);
Task UpdateAsync(UpdateAccountDto account);
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/ICarService.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/ICarService.cs
new file mode 100644
index 00000000..d7b3b75e
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/ICarService.cs
@@ -0,0 +1,14 @@
+using CheckDrive.Application.DTOs.Car;
+using CheckDrive.Application.QueryParameters;
+
+namespace CheckDrive.Application.Interfaces;
+
+public interface ICarService
+{
+ Task> GetAllAsync(CarQueryParameters queryParameters);
+ Task> GetAvailableCarsAsync();
+ Task GetByIdAsync(int id);
+ Task CreateAsync(CreateCarDto car);
+ Task UpdateAsync(UpdateCarDto car);
+ Task DeleteAsync(int id);
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/ICheckPointService.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/ICheckPointService.cs
index bf33dbf6..1c2d1395 100644
--- a/CheckDrive.Api/CheckDrive.Application/Interfaces/ICheckPointService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/ICheckPointService.cs
@@ -6,5 +6,6 @@ namespace CheckDrive.Application.Interfaces;
public interface ICheckPointService
{
Task> GetCheckPointsAsync(CheckPointQueryParameters queryParameters);
- Task GetCheckPointsByDriverIdAsync(int driverId);
+ Task GetCurrentCheckPointByDriverIdAsync(int driverId);
+ Task CancelCheckPointAsync(int id);
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/IDriverService.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/IDriverService.cs
new file mode 100644
index 00000000..ab930b6a
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/IDriverService.cs
@@ -0,0 +1,9 @@
+using CheckDrive.Application.DTOs.Driver;
+
+namespace CheckDrive.Application.Interfaces;
+
+public interface IDriverService
+{
+ Task> GetAvailableDriversAsync();
+ Task CreateReviewConfirmation(DriverReviewConfirmationDto confirmation);
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/IOilMarkService.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/IOilMarkService.cs
new file mode 100644
index 00000000..74ce90ed
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/IOilMarkService.cs
@@ -0,0 +1,13 @@
+using CheckDrive.Application.DTOs.OilMark;
+using CheckDrive.Application.QueryParameters;
+
+namespace CheckDrive.Application.Interfaces;
+
+public interface IOilMarkService
+{
+ Task> GetAllAsync(OilMarkQueryParameters queryParameters);
+ Task GetByIdAsync(int id);
+ Task CreateAsync(CreateOilMarkDto oilMark);
+ Task UpdateAsync(UpdateOilMarkDto oilMark);
+ Task DeleteAsync(int id);
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Interfaces/Review/IReviewHistoryService.cs b/CheckDrive.Api/CheckDrive.Application/Interfaces/Review/IReviewHistoryService.cs
new file mode 100644
index 00000000..8d65ec60
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Interfaces/Review/IReviewHistoryService.cs
@@ -0,0 +1,14 @@
+using CheckDrive.Application.DTOs.CheckPoint;
+using CheckDrive.Application.DTOs.DoctorReview;
+using CheckDrive.Application.DTOs.OperatorReview;
+using CheckDrive.Application.DTOs.Review;
+
+namespace CheckDrive.Application.Interfaces.Review;
+
+public interface IReviewHistoryService
+{
+ Task> GetDriverHistoriesAsync(int driverId);
+ Task> GetDoctorHistoriesAsync(int doctorId);
+ Task> GetMechanicHistoriesAsync(int mechanicId);
+ Task> GetOperatorHistoriesAsync(int operatorId);
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Mappings/AccountMappings.cs b/CheckDrive.Api/CheckDrive.Application/Mappings/AccountMappings.cs
index 500affcb..729f9e46 100644
--- a/CheckDrive.Api/CheckDrive.Application/Mappings/AccountMappings.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Mappings/AccountMappings.cs
@@ -10,7 +10,8 @@ internal sealed class AccountMappings : Profile
public AccountMappings()
{
CreateMap()
- .ForCtorParam(nameof(AccountDto.Id), cfg => cfg.MapFrom(e => e.AccountId))
+ .ForCtorParam(nameof(AccountDto.Id), cfg => cfg.MapFrom(e => e.Id))
+ .ForCtorParam(nameof(AccountDto.AccountId), cfg => cfg.MapFrom(e => e.AccountId))
.ForCtorParam(nameof(AccountDto.Username), cfg => cfg.MapFrom(e => e.Account.UserName))
.ForCtorParam(nameof(AccountDto.PhoneNumber), cfg => cfg.MapFrom(e => e.Account.PhoneNumber))
.ForCtorParam(nameof(AccountDto.Email), cfg => cfg.MapFrom(e => e.Account.Email))
diff --git a/CheckDrive.Api/CheckDrive.Application/Mappings/CarMappings.cs b/CheckDrive.Api/CheckDrive.Application/Mappings/CarMappings.cs
new file mode 100644
index 00000000..67b1778e
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Mappings/CarMappings.cs
@@ -0,0 +1,15 @@
+using AutoMapper;
+using CheckDrive.Application.DTOs.Car;
+using CheckDrive.Domain.Entities;
+
+namespace CheckDrive.Application.Mappings;
+
+public sealed class CarMappings : Profile
+{
+ public CarMappings()
+ {
+ CreateMap();
+ CreateMap();
+ CreateMap();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Mappings/OilMarkMappings.cs b/CheckDrive.Api/CheckDrive.Application/Mappings/OilMarkMappings.cs
new file mode 100644
index 00000000..8df8753f
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Mappings/OilMarkMappings.cs
@@ -0,0 +1,15 @@
+using AutoMapper;
+using CheckDrive.Application.DTOs.OilMark;
+using CheckDrive.Domain.Entities;
+
+namespace CheckDrive.Application.Mappings;
+
+public sealed class OilMarkMappings : Profile
+{
+ public OilMarkMappings()
+ {
+ CreateMap();
+ CreateMap();
+ CreateMap();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/DoctorReviewMappings.cs b/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/DoctorReviewMappings.cs
index a8917df3..b707cc34 100644
--- a/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/DoctorReviewMappings.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/DoctorReviewMappings.cs
@@ -1,7 +1,6 @@
using AutoMapper;
using CheckDrive.Application.DTOs.DoctorReview;
using CheckDrive.Domain.Entities;
-using CheckDrive.Domain.Enums;
namespace CheckDrive.Application.Mappings.Reviews;
@@ -10,13 +9,7 @@ internal sealed class DoctorReviewMappings : Profile
public DoctorReviewMappings()
{
CreateMap()
- .ForCtorParam(nameof(DoctorReviewDto.DriverId), cfg => cfg.MapFrom(e => e.CheckPoint.DoctorReview.DriverId))
- .ForCtorParam(nameof(DoctorReviewDto.DriverName), cfg => cfg.MapFrom(e => $"{e.CheckPoint.DoctorReview.Driver.FirstName} {e.CheckPoint.DoctorReview.Driver.LastName}"))
- .ForCtorParam(nameof(DoctorReviewDto.ReviewerId), cfg => cfg.MapFrom(e => e.DoctorId))
+ .ForCtorParam(nameof(DoctorReviewDto.DriverName), cfg => cfg.MapFrom(e => $"{e.Driver.FirstName} {e.Driver.LastName}"))
.ForCtorParam(nameof(DoctorReviewDto.ReviewerName), cfg => cfg.MapFrom(e => $"{e.Doctor.FirstName} {e.Doctor.LastName}"));
-
- CreateMap()
- .ForMember(x => x.Date, cfg => cfg.MapFrom(_ => DateTime.UtcNow))
- .ForMember(x => x.Status, cfg => cfg.MapFrom(d => d.IsApprovedByReviewer ? ReviewStatus.Approved : ReviewStatus.RejectedByReviewer));
}
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/MechanicHandoverMappings.cs b/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/MechanicHandoverMappings.cs
index f4910d7a..2c435def 100644
--- a/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/MechanicHandoverMappings.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Mappings/Reviews/MechanicHandoverMappings.cs
@@ -10,8 +10,9 @@ internal sealed class MechanicHandoverMappings : Profile
public MechanicHandoverMappings()
{
CreateMap()
+ .ForMember(x => x.MechanicId, cfg => cfg.MapFrom(e => e.ReviewerId))
.ForMember(x => x.Date, cfg => cfg.MapFrom(_ => DateTime.UtcNow))
- .ForMember(x => x.Status, cfg => cfg.MapFrom(x => x.IsApprovedByReviewer ? ReviewStatus.PendingDriverApproval : ReviewStatus.RejectedByReviewer));
+ .ForMember(x => x.Status, cfg => cfg.MapFrom(e => e.IsApprovedByReviewer ? ReviewStatus.PendingDriverApproval : ReviewStatus.RejectedByReviewer));
CreateMap()
.ForCtorParam(nameof(MechanicHandoverReviewDto.DriverId), cfg => cfg.MapFrom(e => e.CheckPoint.DoctorReview.DriverId))
diff --git a/CheckDrive.Api/CheckDrive.Application/QueryParameters/CarQueryParameters.cs b/CheckDrive.Api/CheckDrive.Application/QueryParameters/CarQueryParameters.cs
new file mode 100644
index 00000000..88aafe59
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/QueryParameters/CarQueryParameters.cs
@@ -0,0 +1,7 @@
+using CheckDrive.Domain.Enums;
+
+namespace CheckDrive.Application.QueryParameters;
+
+public record CarQueryParameters(
+ string? SearchText,
+ CarStatus? Status);
diff --git a/CheckDrive.Api/CheckDrive.Application/QueryParameters/OilMarkQueryParameters.cs b/CheckDrive.Api/CheckDrive.Application/QueryParameters/OilMarkQueryParameters.cs
new file mode 100644
index 00000000..f8a0f9e9
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/QueryParameters/OilMarkQueryParameters.cs
@@ -0,0 +1,3 @@
+namespace CheckDrive.Application.QueryParameters;
+
+public sealed record OilMarkQueryParameters(string? SearchText);
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/AccountService.cs b/CheckDrive.Api/CheckDrive.Application/Services/AccountService.cs
index b4bd538b..5f0d44f4 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/AccountService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/AccountService.cs
@@ -1,34 +1,45 @@
using AutoMapper;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
+using CheckDrive.Application.Constants;
using CheckDrive.Application.DTOs.Account;
using CheckDrive.Application.Interfaces;
using CheckDrive.Domain.Entities;
+using CheckDrive.Domain.Enums;
using CheckDrive.Domain.Exceptions;
using CheckDrive.Domain.Interfaces;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services;
internal sealed class AccountService : IAccountService
{
private readonly ICheckDriveDbContext _context;
- private readonly UserManager _userManager;
private readonly IMapper _mapper;
+ private readonly UserManager _userManager;
- public AccountService(ICheckDriveDbContext context, IMapper mapper, UserManager userManager)
+ public AccountService(
+ ICheckDriveDbContext context,
+ IMapper mapper,
+ UserManager userManager)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
}
- public async Task> GetAsync()
+ public async Task> GetAsync(EmployeePosition? position)
{
- var accounts = await _context.Employees
- .AsNoTracking()
+ var query = _context.Employees
.Include(x => x.Account)
- .ToListAsync();
+ .AsNoTracking()
+ .AsQueryable();
+ if (position.HasValue)
+ {
+ query = query.Where(x => x.Position == position.Value);
+ }
+
+ var accounts = await query.ToListAsync();
var dtos = _mapper.Map>(accounts);
return dtos;
@@ -65,6 +76,8 @@ public async Task CreateAsync(CreateAccountDto account)
throw new InvalidOperationException("Could not create user account.");
}
+ await AssignToRoleAsync(user, account.Position);
+
var employee = _mapper.Map(account);
employee.AccountId = user.Id;
@@ -95,4 +108,25 @@ public async Task DeleteAsync(string id)
_context.Users.Remove(account);
await _context.SaveChangesAsync();
}
+
+ private async Task AssignToRoleAsync(IdentityUser user, EmployeePosition position)
+ {
+ var role = position switch
+ {
+ EmployeePosition.Driver => Roles.Driver,
+ EmployeePosition.Doctor => Roles.Doctor,
+ EmployeePosition.Mechanic => Roles.Mechanic,
+ EmployeePosition.Operator => Roles.Operator,
+ EmployeePosition.Dispatcher => Roles.Dispatcher,
+ EmployeePosition.Manager => Roles.Manager,
+ _ => throw new InvalidOperationException("Invalid position for role assignment.")
+ };
+
+ var roleResult = await _userManager.AddToRoleAsync(user, role.ToString());
+
+ if (!roleResult.Succeeded)
+ {
+ throw new InvalidOperationException("Could not create user account.");
+ }
+ }
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/Auth/AuthService.cs b/CheckDrive.Api/CheckDrive.Application/Services/Auth/AuthService.cs
index ca9bb6e0..05ec2c28 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/Auth/AuthService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/Auth/AuthService.cs
@@ -1,7 +1,9 @@
using CheckDrive.Application.DTOs.Identity;
using CheckDrive.Application.Interfaces.Auth;
using CheckDrive.Domain.Exceptions;
+using CheckDrive.Domain.Interfaces;
using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services.Auth;
@@ -9,26 +11,32 @@ internal sealed class AuthService : IAuthService
{
private readonly IJwtTokenGenerator _jwtTokenGenerator;
private readonly UserManager _userManager;
+ private readonly ICheckDriveDbContext _context;
- public AuthService(IJwtTokenGenerator jwtTokenGenerator, UserManager userManager)
+ public AuthService(
+ IJwtTokenGenerator jwtTokenGenerator,
+ UserManager userManager,
+ ICheckDriveDbContext context)
{
_jwtTokenGenerator = jwtTokenGenerator ?? throw new ArgumentNullException(nameof(jwtTokenGenerator));
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
+ _context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task LoginAsync(LoginDto request)
{
ArgumentNullException.ThrowIfNull(request);
- var user = await _userManager.FindByNameAsync(request.UserName);
+ var employee = await _context.Employees.Include(x => x.Account).FirstOrDefaultAsync(x => x.Account.UserName == request.UserName);
- if (user is null || !await _userManager.CheckPasswordAsync(user, request.Password))
+ if (employee is null || !await _userManager.CheckPasswordAsync(employee.Account, request.Password))
{
throw new InvalidLoginAttemptException("Invalid email or password");
}
+ var user = await _userManager.FindByNameAsync(request.UserName);
var roles = await _userManager.GetRolesAsync(user);
- var token = _jwtTokenGenerator.GenerateToken(user, roles);
+ var token = _jwtTokenGenerator.GenerateToken(employee, roles);
return token;
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/CarService.cs b/CheckDrive.Api/CheckDrive.Application/Services/CarService.cs
new file mode 100644
index 00000000..dfb77709
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Services/CarService.cs
@@ -0,0 +1,121 @@
+using AutoMapper;
+using Microsoft.EntityFrameworkCore;
+using CheckDrive.Application.DTOs.Car;
+using CheckDrive.Application.Interfaces;
+using CheckDrive.Domain.Interfaces;
+using CheckDrive.Domain.Exceptions;
+using CheckDrive.Domain.Entities;
+using CheckDrive.Application.QueryParameters;
+using CheckDrive.Domain.Enums;
+using AutoMapper.QueryableExtensions;
+
+namespace CheckDrive.Application.Services;
+
+internal sealed class CarService : ICarService
+{
+ private readonly ICheckDriveDbContext _context;
+ private readonly IMapper _mapper;
+
+ public CarService(ICheckDriveDbContext context, IMapper mapper)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ }
+
+ public async Task> GetAvailableCarsAsync()
+ {
+ var cars = await _context.Cars
+ .Where(x => x.Status == CarStatus.Free)
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ToListAsync();
+
+ return cars;
+ }
+
+ public async Task> GetAllAsync(CarQueryParameters queryParameters)
+ {
+ ArgumentNullException.ThrowIfNull(queryParameters);
+
+ var query = _context.Cars
+ .AsNoTracking()
+ .AsQueryable();
+
+ if (!string.IsNullOrEmpty(queryParameters.SearchText))
+ {
+ query = query.Where(x => x.Model.Contains(queryParameters.SearchText)
+ || x.Color.Contains(queryParameters.SearchText)
+ || x.Number.Contains(queryParameters.SearchText));
+ }
+
+ if (queryParameters.Status.HasValue)
+ {
+ query = query.Where(x => x.Status == queryParameters.Status);
+ }
+
+ var cars = await query
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ToListAsync();
+
+ return cars;
+ }
+
+ public async Task GetByIdAsync(int id)
+ {
+ var car = await _context.Cars.FirstOrDefaultAsync(x => x.Id == id);
+
+ if (car is null)
+ {
+ throw new EntityNotFoundException($"Car with id: {id} is not found.");
+ }
+
+ var dto = _mapper.Map(car);
+
+ return dto;
+ }
+
+ public async Task CreateAsync(CreateCarDto car)
+ {
+ ArgumentNullException.ThrowIfNull(car);
+
+ var entity = _mapper.Map(car);
+
+ _context.Cars.Add(entity);
+ await _context.SaveChangesAsync();
+
+ var dto = _mapper.Map(entity);
+
+ return dto;
+ }
+
+ public async Task UpdateAsync(UpdateCarDto car)
+ {
+ ArgumentNullException.ThrowIfNull(car);
+
+ if (!await _context.Cars.AnyAsync(x => x.Id == car.Id))
+ {
+ throw new EntityNotFoundException($"Car with id: {car.Id} is not found.");
+ }
+
+ var entity = _mapper.Map(car);
+
+ _context.Cars.Update(entity);
+ await _context.SaveChangesAsync();
+
+ var dto = _mapper.Map(entity);
+
+ return dto;
+ }
+
+ public async Task DeleteAsync(int id)
+ {
+ var entity = await _context.Cars.FirstOrDefaultAsync(x => x.Id == id);
+
+ if (entity is null)
+ {
+ throw new EntityNotFoundException($"Car with id: {id} is not found.");
+ }
+
+ _context.Cars.Remove(entity);
+ await _context.SaveChangesAsync();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/CheckPointService.cs b/CheckDrive.Api/CheckDrive.Application/Services/CheckPointService.cs
index 03ce4d1c..4edf3bcd 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/CheckPointService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/CheckPointService.cs
@@ -1,18 +1,12 @@
using AutoMapper;
+using Microsoft.EntityFrameworkCore;
using CheckDrive.Application.DTOs.CheckPoint;
-using CheckDrive.Application.DTOs.Debt;
-using CheckDrive.Application.DTOs.DispatcherReview;
-using CheckDrive.Application.DTOs.DoctorReview;
-using CheckDrive.Application.DTOs.MechanicAcceptance;
-using CheckDrive.Application.DTOs.MechanicHandover;
-using CheckDrive.Application.DTOs.OperatorReview;
-using CheckDrive.Application.DTOs.Review;
using CheckDrive.Application.Interfaces;
using CheckDrive.Domain.Entities;
using CheckDrive.Domain.Enums;
+using CheckDrive.Domain.Exceptions;
using CheckDrive.Domain.Interfaces;
using CheckDrive.Domain.QueryParameters;
-using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services;
@@ -38,12 +32,39 @@ public async Task> GetCheckPointsAsync(CheckPointQueryParame
return dtos;
}
- public Task GetCheckPointsByDriverIdAsync(int driverId)
+ public async Task GetCurrentCheckPointByDriverIdAsync(int driverId)
+ {
+ var checkPoint = await GetQuery()
+ .Where(x => x.DoctorReview != null)
+ .Where(x => x.DoctorReview.DriverId == driverId)
+ .Where(x => x.StartDate.Date == DateTime.UtcNow.Date)
+ .Where(x => x.Status == CheckPointStatus.InProgress)
+ .FirstOrDefaultAsync();
+
+ if (checkPoint is null)
+ {
+ throw new EntityNotFoundException($"Driver with id: {driverId} does not have current active Check Point.");
+ }
+
+ var dto = _mapper.Map(checkPoint);
+
+ return dto;
+ }
+
+ public async Task CancelCheckPointAsync(int id)
{
- throw new NotImplementedException();
+ var checkPoint = await GetAndValidateAsync(id);
+
+ if (checkPoint.Status == CheckPointStatus.Completed || checkPoint.Status == CheckPointStatus.AutomaticallyClosed)
+ {
+ throw new InvalidOperationException($"Cannot cancel closed Check Point.");
+ }
+
+ checkPoint.Status = CheckPointStatus.ClosedByManager;
+ await _context.SaveChangesAsync();
}
- private IQueryable GetQuery(CheckPointQueryParameters queryParameters)
+ private IQueryable GetQuery(CheckPointQueryParameters? queryParameters = null)
{
var query = _context.CheckPoints
.AsNoTracking()
@@ -53,15 +74,21 @@ private IQueryable GetQuery(CheckPointQueryParameters queryParameter
.ThenInclude(x => x.Doctor)
.Include(x => x.MechanicHandover)
.ThenInclude(x => x.Mechanic)
+ .Include(x => x.MechanicHandover)
+ .ThenInclude(x => x.Car)
.Include(x => x.OperatorReview)
.ThenInclude(x => x.Operator)
.Include(x => x.MechanicAcceptance)
.ThenInclude(x => x.Mechanic)
.Include(x => x.DispatcherReview)
.ThenInclude(x => x.Dispatcher)
- .Include(x => x.Debt)
.AsQueryable();
+ if (queryParameters is null)
+ {
+ return query;
+ }
+
if (!string.IsNullOrWhiteSpace(queryParameters.Search))
{
query = query.Where(x =>
@@ -88,9 +115,9 @@ private IQueryable GetQuery(CheckPointQueryParameters queryParameter
query = query.Where(x => x.Stage == queryParameters.Stage.Value);
}
- if (queryParameters.DateFilter.HasValue)
+ if (queryParameters.Date.HasValue)
{
- query = FilterByDate(query, queryParameters.DateFilter.Value);
+ query = FilterByDate(query, queryParameters.Date.Value);
}
return query;
@@ -108,57 +135,15 @@ private static IQueryable FilterByDate(IQueryable query,
};
}
- private List GetReviews(CheckPoint checkPoint)
+ private async Task GetAndValidateAsync(int id)
{
- ArgumentNullException.ThrowIfNull(checkPoint);
-
- var reviews = new List();
- var doctorReview = _mapper.Map(checkPoint.DoctorReview);
- reviews.Add(doctorReview);
+ var checkPoint = await _context.CheckPoints.FirstOrDefaultAsync(x => x.Id == id);
- if (checkPoint.MechanicHandover is not null)
+ if (checkPoint is null)
{
- var mechanicHandover = _mapper.Map(checkPoint.MechanicHandover);
- reviews.Add(mechanicHandover);
+ throw new EntityNotFoundException($"Check Point with id: {id} is not found.");
}
- if (checkPoint.OperatorReview is not null)
- {
- var operatorReview = _mapper.Map(checkPoint.OperatorReview);
- reviews.Add(operatorReview);
- }
-
- if (checkPoint.MechanicAcceptance is not null)
- {
- var mechanicAcceptance = _mapper.Map(checkPoint.MechanicAcceptance);
- reviews.Add(mechanicAcceptance);
- }
-
- if (checkPoint.DispatcherReview is not null)
- {
- var dispatcherReview = _mapper.Map(checkPoint.DispatcherReview);
- reviews.Add(dispatcherReview);
- }
-
- return reviews;
- }
-
- private static DebtDto? GetDebt(CheckPoint checkPoint)
- {
- ArgumentNullException.ThrowIfNull(checkPoint);
-
- if (checkPoint.Debt is null)
- {
- return null;
- }
-
- var debtEntity = checkPoint.Debt;
- var debtDto = new DebtDto(
- CheckPointId: checkPoint.Id,
- FualAmount: debtEntity.FuelAmount,
- PaidAmount: debtEntity.PaidAmount,
- Status: debtEntity.Status);
-
- return debtDto;
+ return checkPoint;
}
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/DriverService.cs b/CheckDrive.Api/CheckDrive.Application/Services/DriverService.cs
new file mode 100644
index 00000000..1686e8dd
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Services/DriverService.cs
@@ -0,0 +1,193 @@
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using CheckDrive.Application.DTOs.Driver;
+using CheckDrive.Application.Interfaces;
+using CheckDrive.Domain.Entities;
+using CheckDrive.Domain.Enums;
+using CheckDrive.Domain.Exceptions;
+using CheckDrive.Domain.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace CheckDrive.Application.Services;
+
+internal sealed class DriverService : IDriverService
+{
+ private readonly ICheckDriveDbContext _context;
+ private readonly IMapper _mapper;
+
+ public DriverService(ICheckDriveDbContext context, IMapper mapper)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ }
+
+ public async Task> GetAvailableDriversAsync()
+ {
+ var drivers = await _context.Drivers
+ .Where(x => !x.Reviews.Any(x => x.CheckPoint.Status == CheckPointStatus.InProgress))
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ToListAsync();
+
+ return drivers;
+ }
+
+ public async Task CreateReviewConfirmation(DriverReviewConfirmationDto confirmation)
+ {
+ ArgumentNullException.ThrowIfNull(confirmation);
+
+ var checkPoint = await GetAndValidateCheckPointAsync(confirmation.CheckPointId);
+
+ if (!confirmation.IsAcceptedByDriver)
+ {
+ checkPoint.Status = CheckPointStatus.InterruptedByDriverRejection;
+ await _context.SaveChangesAsync();
+
+ return;
+ }
+
+ await CreateReviewAsync(checkPoint, confirmation);
+ await _context.SaveChangesAsync();
+ }
+
+ private async Task CreateReviewAsync(CheckPoint checkPoint, DriverReviewConfirmationDto confirmation)
+ {
+ switch (confirmation.ReviewType)
+ {
+ case ReviewType.MechanicHandover:
+ await AcceptMechanicHandoverAsync(checkPoint);
+ break;
+ case ReviewType.OperatorReview:
+ await AcceptOperatorReviewAsync(checkPoint);
+ break;
+ case ReviewType.MechanicAcceptance:
+ await AcceptMechanicAcceptanceAsync(checkPoint);
+ break;
+ }
+ }
+
+ private async Task AcceptMechanicHandoverAsync(CheckPoint checkPoint)
+ {
+ if (checkPoint.MechanicHandover is null)
+ {
+ throw new InvalidOperationException($"Cannot update car for Check Point without Mechanic Handover Review.");
+ }
+
+ var car = await GetAndValidateCarAsync(checkPoint.MechanicHandover.CarId);
+
+ car.Mileage = checkPoint.MechanicHandover.InitialMileage;
+ car.Status = CarStatus.Busy;
+ checkPoint.Stage = CheckPointStage.MechanicHandover;
+ checkPoint.MechanicHandover.Status = ReviewStatus.Approved;
+ }
+
+ private async Task AcceptOperatorReviewAsync(CheckPoint checkPoint)
+ {
+ if (checkPoint.OperatorReview is null || checkPoint.MechanicHandover is null)
+ {
+ throw new InvalidOperationException($"Cannot update car for Check Point without Operator Review or Mechanic Review.");
+ }
+
+ var car = await GetAndValidateCarAsync(checkPoint.MechanicHandover.CarId);
+
+ // TODO: Add alert check in case the difference between initial oil amount specified by reviewer
+ // is much larger than what was specified yesterday. For example if at the end of the previoius
+ // ride of a car there was 10l of oil remaining and now reviewer says that there is 5l or 0l remaining
+ // rise an alert so that manager reviews and confirm this process
+ car.RemainingFuel = checkPoint.OperatorReview.InitialOilAmount + checkPoint.OperatorReview.OilRefillAmount;
+ checkPoint.Stage = CheckPointStage.OperatorReview;
+ checkPoint.OperatorReview.Status = ReviewStatus.Approved;
+ }
+
+ private async Task AcceptMechanicAcceptanceAsync(CheckPoint checkPoint)
+ {
+ if (checkPoint.MechanicHandover is null || checkPoint.MechanicAcceptance is null)
+ {
+ throw new InvalidOperationException("Cannot perform Mechanic Acceptance without Mechanic Handover Review.");
+ }
+
+ var car = await GetAndValidateCarAsync(checkPoint.MechanicHandover.CarId);
+ var fuelConsumption = CalculateFuelConsumption(
+ checkPoint.MechanicHandover.InitialMileage,
+ checkPoint.MechanicAcceptance.FinalMileage,
+ car.AverageFuelConsumption);
+
+ if (fuelConsumption > car.RemainingFuel)
+ {
+ var debt = CreateDebt(
+ checkPoint,
+ fuelConsumption,
+ car.RemainingFuel,
+ checkPoint.MechanicAcceptance.RemainingFuelAmount);
+ _context.Debts.Add(debt);
+ }
+
+ car.Mileage = checkPoint.MechanicAcceptance.FinalMileage;
+ car.RemainingFuel = checkPoint.MechanicAcceptance.RemainingFuelAmount;
+ car.CurrentMonthMileage += checkPoint.MechanicHandover.InitialMileage - checkPoint.MechanicAcceptance.FinalMileage;
+ car.CurrentYearMileage += checkPoint.MechanicHandover.InitialMileage - checkPoint.MechanicAcceptance.FinalMileage;
+ car.CurrentMonthFuelConsumption += fuelConsumption;
+ car.CurrentYearFuelConsumption += fuelConsumption;
+
+ checkPoint.Stage = CheckPointStage.MechanicAcceptance;
+ checkPoint.MechanicAcceptance.Status = ReviewStatus.Approved;
+ }
+
+ private async Task GetAndValidateCheckPointAsync(int checkPointId)
+ {
+ var checkPoint = await _context.CheckPoints
+ .Include(x => x.MechanicHandover)
+ .Include(x => x.OperatorReview)
+ .Include(x => x.MechanicAcceptance)
+ .FirstOrDefaultAsync(x => x.Id == checkPointId);
+
+ if (checkPoint is null)
+ {
+ throw new InvalidOperationException($"Check Point with id: {checkPointId} is not found.");
+ }
+
+ if (checkPoint.Status != CheckPointStatus.InProgress)
+ {
+ throw new InvalidOperationException(
+ $"Driver can perform review confirmation only for In Progress Check Point. Current check point status: {checkPoint.Status}");
+ }
+
+ return checkPoint;
+ }
+
+ private async Task GetAndValidateCarAsync(int carId)
+ {
+ var car = await _context.Cars
+ .FirstOrDefaultAsync(x => x.Id == carId);
+
+ if (car is null)
+ {
+ throw new EntityNotFoundException($"Car with id: {carId} is not found.");
+ }
+
+ return car;
+ }
+
+ private static decimal CalculateFuelConsumption(
+ int initialMileage,
+ int finalMileage,
+ decimal averageFuelConsumption)
+ {
+ var mileageAmount = finalMileage - initialMileage;
+ var fuelSpent = averageFuelConsumption * mileageAmount;
+
+ return fuelSpent;
+ }
+
+ private static Debt CreateDebt(CheckPoint checkPoint, decimal fuelSpent, decimal fuelInitialAmount, decimal fuelFinalAmount)
+ {
+ var debt = new Debt
+ {
+ CheckPoint = checkPoint,
+ FuelAmount = fuelSpent - fuelInitialAmount - fuelFinalAmount,
+ PaidAmount = 0,
+ Status = DebtStatus.Unpaid
+ };
+
+ return debt;
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/EmployeeMappings.cs b/CheckDrive.Api/CheckDrive.Application/Services/EmployeeMappings.cs
new file mode 100644
index 00000000..09d9625b
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Services/EmployeeMappings.cs
@@ -0,0 +1,14 @@
+using AutoMapper;
+using CheckDrive.Application.DTOs.Driver;
+using CheckDrive.Domain.Entities;
+
+namespace CheckDrive.Application.Services;
+
+public sealed class EmployeeMappings : Profile
+{
+ public EmployeeMappings()
+ {
+ CreateMap()
+ .ForMember(x => x.FullName, cfg => cfg.MapFrom(e => $"{e.FirstName} {e.LastName}"));
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/OilMarkService.cs b/CheckDrive.Api/CheckDrive.Application/Services/OilMarkService.cs
new file mode 100644
index 00000000..b7ddaf93
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Services/OilMarkService.cs
@@ -0,0 +1,106 @@
+using AutoMapper;
+using CheckDrive.Application.DTOs.OilMark;
+using CheckDrive.Application.Interfaces;
+using CheckDrive.Application.QueryParameters;
+using CheckDrive.Domain.Entities;
+using CheckDrive.Domain.Exceptions;
+using CheckDrive.Domain.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace CheckDrive.Application.Services;
+
+internal sealed class OilMarkService : IOilMarkService
+{
+ private readonly ICheckDriveDbContext _context;
+ private readonly IMapper _mapper;
+
+ public OilMarkService(ICheckDriveDbContext context, IMapper mapper)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ }
+
+ public async Task CreateAsync(CreateOilMarkDto oilMark)
+ {
+ ArgumentNullException.ThrowIfNull(oilMark);
+
+ var entity = _mapper.Map(oilMark);
+
+ _context.OilMarks.Add(entity);
+ await _context.SaveChangesAsync();
+
+ var dto = _mapper.Map(entity);
+
+ return dto;
+ }
+
+ public async Task DeleteAsync(int id)
+ {
+ var entity = await _context.OilMarks.FirstOrDefaultAsync(x => x.Id == id);
+
+ if (entity is null)
+ {
+ throw new EntityNotFoundException($"Oil mark with id: {id} is not found.");
+ }
+
+ _context.OilMarks.Remove(entity);
+ await _context.SaveChangesAsync();
+ }
+
+ public Task> GetAllAsync()
+ {
+ var oilMarks = _context.OilMarks
+ .Select(x => new OilMarkDto(x.Id, x.Name))
+ .ToListAsync();
+
+ return oilMarks;
+ }
+
+ public async Task> GetAllAsync(OilMarkQueryParameters queryParameters)
+ {
+ ArgumentNullException.ThrowIfNull(queryParameters);
+
+ var query = _context.OilMarks.AsNoTracking().AsQueryable();
+
+ if (!string.IsNullOrEmpty(queryParameters.SearchText))
+ {
+ query = query.Where(x => x.Name.Contains(queryParameters.SearchText));
+ }
+
+ var entities = await query.ToListAsync();
+ var dtos = _mapper.Map>(entities);
+
+ return dtos;
+ }
+
+ public async Task GetByIdAsync(int id)
+ {
+ var entity = await _context.OilMarks.FirstOrDefaultAsync(x => x.Id == id);
+
+ if (entity is null)
+ {
+ throw new EntityNotFoundException($"Oil mark with id: {id} is not found.");
+ }
+
+ var dto = _mapper.Map(entity);
+
+ return dto;
+ }
+
+ public async Task UpdateAsync(UpdateOilMarkDto oilMark)
+ {
+ if (!await _context.OilMarks.AnyAsync(x => x.Id == oilMark.Id))
+ {
+ throw new EntityNotFoundException($"Oil mark with id: {oilMark.Id} is not found.");
+ }
+
+ var entity = _mapper.Map(oilMark);
+
+ _context.OilMarks.Update(entity);
+ await _context.SaveChangesAsync();
+
+ var dto = _mapper.Map(entity);
+
+ return dto;
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/Review/DispatcherReviewService.cs b/CheckDrive.Api/CheckDrive.Application/Services/Review/DispatcherReviewService.cs
index db0d7cf1..9a140fd9 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/Review/DispatcherReviewService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/Review/DispatcherReviewService.cs
@@ -29,7 +29,7 @@ public async Task CreateAsync(CreateDispatcherReviewDto rev
UpdateCheckPoint(checkPoint, review);
- var reviewEntity = CreateReviewEntity(checkPoint, dispatcher, review);
+ var reviewEntity = CreateReview(checkPoint, dispatcher, review);
_context.DispatcherReviews.Add(reviewEntity);
await _context.SaveChangesAsync();
@@ -42,9 +42,6 @@ public async Task CreateAsync(CreateDispatcherReviewDto rev
private async Task GetAndValidateCheckPointAsync(int checkPointId)
{
var checkPoint = await _context.CheckPoints
- .Include(x => x.MechanicAcceptance)
- .Include(x => x.MechanicHandover)
- .ThenInclude(mh => mh!.Car)
.FirstOrDefaultAsync(x => x.Id == checkPointId);
if (checkPoint is null)
@@ -80,10 +77,27 @@ private async Task GetAndValidateDispatcherAsync(int dispatcherId)
return dispatcher;
}
- private static DispatcherReview CreateReviewEntity(CheckPoint checkPoint, Dispatcher dispatcher, CreateDispatcherReviewDto review)
+ private static void UpdateCheckPoint(CheckPoint checkPoint, CreateDispatcherReviewDto review)
{
ArgumentNullException.ThrowIfNull(checkPoint);
+ checkPoint.Stage = CheckPointStage.DispatcherReview;
+
+ if (!review.IsApprovedByReviewer || review.FuelConsumptionAdjustment.HasValue || review.FinalMileageAdjustment.HasValue)
+ {
+ checkPoint.Status = CheckPointStatus.PendingManagerReview;
+ }
+ else
+ {
+ checkPoint.Status = CheckPointStatus.Completed;
+ }
+ }
+
+ private static DispatcherReview CreateReview(CheckPoint checkPoint, Dispatcher dispatcher, CreateDispatcherReviewDto review)
+ {
+ ArgumentNullException.ThrowIfNull(checkPoint);
+ ArgumentNullException.ThrowIfNull(dispatcher);
+
var entity = new DispatcherReview
{
CheckPoint = checkPoint,
@@ -92,30 +106,9 @@ private static DispatcherReview CreateReviewEntity(CheckPoint checkPoint, Dispat
Date = DateTime.UtcNow,
Status = review.IsApprovedByReviewer ? ReviewStatus.Approved : ReviewStatus.RejectedByReviewer,
FuelConsumptionAdjustment = review.FuelConsumptionAdjustment,
- DistanceTravelledAdjustment = review.DistanceTravelledAdjustment,
+ FinalMileageAdjustment = review.FinalMileageAdjustment,
};
return entity;
}
-
- private static void UpdateCheckPoint(CheckPoint checkPoint, CreateDispatcherReviewDto review)
- {
- ArgumentNullException.ThrowIfNull(checkPoint);
-
- checkPoint.Stage = CheckPointStage.DispatcherReview;
-
- if (review.FuelConsumptionAdjustment.HasValue || review.DistanceTravelledAdjustment.HasValue)
- {
- checkPoint.Stage = CheckPointStage.ManagerReview;
- return;
- }
-
- if (!review.IsApprovedByReviewer)
- {
- checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
- return;
- }
-
- checkPoint.Status = CheckPointStatus.Completed;
- }
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/Review/DoctorReviewService.cs b/CheckDrive.Api/CheckDrive.Application/Services/Review/DoctorReviewService.cs
index f0f220e8..907451b9 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/Review/DoctorReviewService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/Review/DoctorReviewService.cs
@@ -1,10 +1,12 @@
using AutoMapper;
using CheckDrive.Application.DTOs.DoctorReview;
+using CheckDrive.Application.Hubs;
using CheckDrive.Application.Interfaces.Review;
using CheckDrive.Domain.Entities;
using CheckDrive.Domain.Enums;
using CheckDrive.Domain.Exceptions;
using CheckDrive.Domain.Interfaces;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services.Review;
@@ -13,11 +15,16 @@ internal sealed class DoctorReviewService : IDoctorReviewService
{
private readonly ICheckDriveDbContext _context;
private readonly IMapper _mapper;
+ private readonly IHubContext _reviewHub;
- public DoctorReviewService(ICheckDriveDbContext context, IMapper mapper)
+ public DoctorReviewService(
+ ICheckDriveDbContext context,
+ IMapper mapper,
+ IHubContext reviewHub)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ _reviewHub = reviewHub ?? throw new ArgumentNullException(nameof(reviewHub));
}
public async Task CreateAsync(CreateDoctorReviewDto review)
@@ -28,13 +35,17 @@ public async Task CreateAsync(CreateDoctorReviewDto review)
var driver = await GetAndValidateDriverAsync(review.DriverId);
var checkPoint = CreateCheckPoint(review);
- var reviewEntity = CreateReviewEntity(review, checkPoint, doctor, driver);
+ var reviewEntity = CreateReview(review, checkPoint, doctor, driver);
_context.DoctorReviews.Add(reviewEntity);
await _context.SaveChangesAsync();
var dto = _mapper.Map(reviewEntity);
+ await _reviewHub.Clients
+ .User(dto.DriverId.ToString())
+ .NotifyDoctorReview(dto);
+
return dto;
}
@@ -61,6 +72,15 @@ private async Task GetAndValidateDriverAsync(int driverId)
throw new EntityNotFoundException($"Driver with id: {driverId} is not found.");
}
+ var hasActiveCheckPoint = await _context.CheckPoints
+ .Where(x => x.DoctorReview.DriverId == driverId)
+ .AnyAsync(x => x.Status == CheckPointStatus.InProgress);
+
+ if (hasActiveCheckPoint)
+ {
+ throw new InvalidOperationException($"Cannot start new Check Point for Driver with active Check Point.");
+ }
+
return driver;
}
@@ -79,7 +99,11 @@ private static CheckPoint CreateCheckPoint(CreateDoctorReviewDto review)
return checkPoint;
}
- private static DoctorReview CreateReviewEntity(CreateDoctorReviewDto review, CheckPoint checkPoint, Doctor doctor, Driver driver)
+ private static DoctorReview CreateReview(
+ CreateDoctorReviewDto review,
+ CheckPoint checkPoint,
+ Doctor doctor,
+ Driver driver)
{
ArgumentNullException.ThrowIfNull(review);
ArgumentNullException.ThrowIfNull(checkPoint);
@@ -87,10 +111,10 @@ private static DoctorReview CreateReviewEntity(CreateDoctorReviewDto review, Che
var doctorReview = new DoctorReview
{
CheckPoint = checkPoint,
- Doctor = doctor,
Driver = driver,
- Date = DateTime.UtcNow,
+ Doctor = doctor,
Notes = review.Notes,
+ Date = DateTime.UtcNow,
Status = review.IsApprovedByReviewer ? ReviewStatus.Approved : ReviewStatus.RejectedByReviewer
};
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicAcceptanceService.cs b/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicAcceptanceService.cs
index 3d1b8e24..bf3ea8f1 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicAcceptanceService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicAcceptanceService.cs
@@ -1,10 +1,12 @@
using AutoMapper;
using CheckDrive.Application.DTOs.MechanicAcceptance;
+using CheckDrive.Application.Hubs;
using CheckDrive.Application.Interfaces.Review;
using CheckDrive.Domain.Entities;
using CheckDrive.Domain.Enums;
using CheckDrive.Domain.Exceptions;
using CheckDrive.Domain.Interfaces;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services.Review;
@@ -13,11 +15,16 @@ internal sealed class MechanicAcceptanceService : IMechanicAcceptanceService
{
private readonly ICheckDriveDbContext _context;
private readonly IMapper _mapper;
+ private readonly IHubContext _hubContext;
- public MechanicAcceptanceService(ICheckDriveDbContext context, IMapper mapper)
+ public MechanicAcceptanceService(
+ ICheckDriveDbContext context,
+ IMapper mapper,
+ IHubContext hubContext)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
}
public async Task CreateAsync(CreateMechanicAcceptanceReviewDto review)
@@ -27,37 +34,33 @@ public async Task CreateAsync(CreateMechanicAccepta
var checkPoint = await GetAndValidateCheckPointAsync(review.CheckPointId);
var mechanic = await GetAndValidateMechanicAsync(review.ReviewerId);
- using var transaction = _context.BeginTransaction();
-
- try
+ if (!review.IsApprovedByReviewer)
{
- UpdateCheckPoint(checkPoint, review);
- UpdateCar(checkPoint, review);
+ checkPoint.Stage = CheckPointStage.MechanicAcceptance;
+ checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
+ }
- var reviewEntity = CreateReviewEntity(checkPoint, mechanic, review);
+ var reviewEntity = CreateReview(checkPoint, mechanic, review);
- var createdReview = _context.MechanicAcceptances.Add(reviewEntity).Entity;
- await _context.SaveChangesAsync();
+ _context.MechanicAcceptances.Add(reviewEntity);
+ await _context.SaveChangesAsync();
- await transaction.CommitAsync();
+ var dto = _mapper.Map(reviewEntity);
- var dto = _mapper.Map(createdReview);
+ await _hubContext.Clients
+ .User(dto.DriverId.ToString())
+ .MechanicAcceptanceConfirmation(dto);
- return dto;
- }
- catch (Exception)
- {
- await transaction.RollbackAsync();
- throw;
- }
+ return dto;
}
private async Task GetAndValidateCheckPointAsync(int checkPointId)
{
var checkPoint = await _context.CheckPoints
- .Include(x => x.OperatorReview)
+ .Include(x => x.DoctorReview)
.Include(x => x.MechanicHandover)
- .ThenInclude(mh => mh!.Car)
+ .ThenInclude(x => x.Car)
+ .Include(x => x.OperatorReview)
.FirstOrDefaultAsync(x => x.Id == checkPointId);
if (checkPoint is null)
@@ -94,7 +97,7 @@ private async Task GetAndValidateMechanicAsync(int mechanicId)
return mechanic;
}
- private static MechanicAcceptance CreateReviewEntity(
+ private static MechanicAcceptance CreateReview(
CheckPoint checkPoint,
Mechanic mechanic,
CreateMechanicAcceptanceReviewDto review)
@@ -112,71 +115,4 @@ private static MechanicAcceptance CreateReviewEntity(
return entity;
}
-
- private static void UpdateCheckPoint(CheckPoint checkPoint, CreateMechanicAcceptanceReviewDto review)
- {
- ArgumentNullException.ThrowIfNull(checkPoint);
-
- checkPoint.Stage = CheckPointStage.MechanicAcceptance;
-
- if (!review.IsApprovedByReviewer)
- {
- checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
- }
- }
-
- private void UpdateCar(CheckPoint checkPoint, CreateMechanicAcceptanceReviewDto review)
- {
- if (checkPoint.MechanicHandover is null)
- {
- throw new InvalidOperationException($"Mechanic Handover in Check Point cannot be null.");
- }
-
- var car = checkPoint.MechanicHandover.Car;
-
- if (car.FuelCapacity < review.RemainingFuelAmount)
- {
- throw new FuelAmountExceedsCarCapacityException($"Remaining amount: {review.RemainingFuelAmount} exceeds Capacity: {car.FuelCapacity}.");
- }
- var fuelSpent = CalculateFuelConsumption(checkPoint.MechanicHandover.InitialMileage, review.FinalMileage, car.AverageFuelConsumption);
-
- if (fuelSpent > car.RemainingFuel)
- {
- var debt = CreateDebt(checkPoint, fuelSpent, car.RemainingFuel, review.RemainingFuelAmount);
- _context.Debts.Add(debt);
- }
- car.RemainingFuel = review.RemainingFuelAmount;
-
- if (review.FinalMileage < car.Mileage)
- {
- throw new InvalidMileageException(
- $"Final mileage ({review.FinalMileage}) cannot be less than the current milea of a car ({car.Mileage}).");
- }
- car.Mileage = review.FinalMileage;
- car.Status = CarStatus.Free;
- }
-
- private static decimal CalculateFuelConsumption(
- int initialMileage,
- int finalMileage,
- decimal averageFuelConsumption)
- {
- var mileageAmount = finalMileage - initialMileage;
- var fuelSpent = averageFuelConsumption * mileageAmount;
-
- return fuelSpent;
- }
-
- private static Debt CreateDebt(CheckPoint checkPoint, decimal fuelSpent, decimal fuelInitialAmount, decimal fuelFinalAmount)
- {
- var debt = new Debt
- {
- CheckPoint = checkPoint,
- FuelAmount = fuelSpent - fuelInitialAmount - fuelFinalAmount,
- PaidAmount = 0,
- Status = DebtStatus.Unpaid
- };
-
- return debt;
- }
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicHandoverService.cs b/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicHandoverService.cs
index 05902a9f..db31ff7e 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicHandoverService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/Review/MechanicHandoverService.cs
@@ -1,10 +1,12 @@
using AutoMapper;
using CheckDrive.Application.DTOs.MechanicHandover;
+using CheckDrive.Application.Hubs;
using CheckDrive.Application.Interfaces.Review;
using CheckDrive.Domain.Entities;
using CheckDrive.Domain.Enums;
using CheckDrive.Domain.Exceptions;
using CheckDrive.Domain.Interfaces;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services.Review;
@@ -13,11 +15,16 @@ internal sealed class MechanicHandoverService : IMechanicHandoverService
{
private readonly ICheckDriveDbContext _context;
private readonly IMapper _mapper;
+ private readonly IHubContext _hubContext;
- public MechanicHandoverService(ICheckDriveDbContext context, IMapper mapper)
+ public MechanicHandoverService(
+ ICheckDriveDbContext context,
+ IMapper mapper,
+ IHubContext hubContext)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
}
public async Task CreateAsync(CreateMechanicHandoverReviewDto review)
@@ -28,41 +35,34 @@ public async Task CreateAsync(CreateMechanicHandoverR
var mechanic = await GetAndValidateMechanicAsync(review.ReviewerId);
var car = await GetAndValidateCarAsync(review.CarId);
- UpdateCheckPoint(checkPoint, review);
- UpdateCar(car, review);
+ if (!review.IsApprovedByReviewer)
+ {
+ checkPoint.Stage = CheckPointStage.MechanicHandover;
+ checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
+ }
- var reviewEntity = CreateReviewEntity(review, mechanic, car, checkPoint);
+ var reviewEntity = CreateReview(review, checkPoint, mechanic, car);
- var createdReview = _context.MechanicHandovers.Add(reviewEntity).Entity;
+ _context.MechanicHandovers.Add(reviewEntity);
await _context.SaveChangesAsync();
- var dto = _mapper.Map(createdReview);
-
- return dto;
- }
-
- private async Task GetAndValidateMechanicAsync(int mechanicId)
- {
- var mechanic = await _context.Mechanics
- .FirstOrDefaultAsync(x => x.Id == mechanicId);
+ var dto = _mapper.Map(reviewEntity);
- if (mechanic is null)
- {
- throw new EntityNotFoundException($"Mechanic with id: {mechanicId} is not found.");
- }
+ await _hubContext.Clients
+ .User(dto.DriverId.ToString())
+ .MechanicHandoverConfirmation(dto);
- return mechanic;
+ return dto;
}
private async Task GetAndValidateCheckPointAsync(int checkPointId)
{
var checkPoint = await _context.CheckPoints
- .Include(x => x.DoctorReview)
.FirstOrDefaultAsync(x => x.Id == checkPointId);
if (checkPoint == null)
{
- throw new InvalidOperationException($"Cannot start mechanic review without doctor's review present.");
+ throw new InvalidOperationException($"Cannot start mechanic review without Doctor Review.");
}
if (checkPoint.Stage != CheckPointStage.DoctorReview)
@@ -70,14 +70,27 @@ private async Task GetAndValidateCheckPointAsync(int checkPointId)
throw new InvalidOperationException($"Cannot start car handover review when check point stage is not Doctor Review");
}
- if (checkPoint.DoctorReview.Status != ReviewStatus.Approved)
+ if (checkPoint.Status != CheckPointStatus.InProgress)
{
- throw new InvalidOperationException($"Cannot start car handover review when Doctor review is not approved.");
+ throw new InvalidOperationException($"Cannot start car handover review when Doctor Review is not approved.");
}
return checkPoint;
}
+ private async Task GetAndValidateMechanicAsync(int mechanicId)
+ {
+ var mechanic = await _context.Mechanics
+ .FirstOrDefaultAsync(x => x.Id == mechanicId);
+
+ if (mechanic is null)
+ {
+ throw new EntityNotFoundException($"Mechanic with id: {mechanicId} is not found.");
+ }
+
+ return mechanic;
+ }
+
private async Task GetAndValidateCarAsync(int carId)
{
var car = await _context.Cars
@@ -90,24 +103,32 @@ private async Task GetAndValidateCarAsync(int carId)
if (car.Status != CarStatus.Free)
{
- throw new UnavailableCarException($"Car with id: {carId} is not available for handover.");
+ throw new CarUnavailableException($"Car with id: {carId} is not available for handover.");
+ }
+
+ if (car.Mileage >= car.YearlyDistanceLimit)
+ {
+ throw new CarUnavailableException($"Car with id: {carId} has reached distance limit and is not available for ride.");
}
return car;
}
- private static MechanicHandover CreateReviewEntity(
+ private static MechanicHandover CreateReview(
CreateMechanicHandoverReviewDto review,
+ CheckPoint checkPoint,
Mechanic mechanic,
- Car car,
- CheckPoint checkPoint)
+ Car car)
{
+ ArgumentNullException.ThrowIfNull(review);
+ ArgumentNullException.ThrowIfNull(checkPoint);
+
var entity = new MechanicHandover()
{
+ InitialMileage = review.InitialMileage,
CheckPoint = checkPoint,
Car = car,
Mechanic = mechanic,
- InitialMileage = review.InitialMileage,
Notes = review.Notes,
Date = DateTime.UtcNow,
Status = review.IsApprovedByReviewer ? ReviewStatus.PendingDriverApproval : ReviewStatus.RejectedByReviewer
@@ -115,30 +136,4 @@ private static MechanicHandover CreateReviewEntity(
return entity;
}
-
- private static void UpdateCar(Car car, CreateMechanicHandoverReviewDto review)
- {
- ArgumentNullException.ThrowIfNull(car);
-
- if (review.InitialMileage < car.Mileage)
- {
- throw new InvalidMileageException(
- $"Initial mileage ({review.InitialMileage}) cannot be less than current mileage of car: {car.Mileage}.");
- }
-
- car.Mileage = review.InitialMileage;
- car.Status = CarStatus.Busy;
- }
-
- private static void UpdateCheckPoint(CheckPoint checkPoint, CreateMechanicHandoverReviewDto review)
- {
- ArgumentNullException.ThrowIfNull(checkPoint);
-
- checkPoint.Stage = CheckPointStage.MechanicHandover;
-
- if (!review.IsApprovedByReviewer)
- {
- checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
- }
- }
}
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/Review/OperatorReviewService.cs b/CheckDrive.Api/CheckDrive.Application/Services/Review/OperatorReviewService.cs
index e40583d1..e52790a3 100644
--- a/CheckDrive.Api/CheckDrive.Application/Services/Review/OperatorReviewService.cs
+++ b/CheckDrive.Api/CheckDrive.Application/Services/Review/OperatorReviewService.cs
@@ -1,10 +1,12 @@
using AutoMapper;
using CheckDrive.Application.DTOs.OperatorReview;
+using CheckDrive.Application.Hubs;
using CheckDrive.Application.Interfaces.Review;
using CheckDrive.Domain.Entities;
using CheckDrive.Domain.Enums;
using CheckDrive.Domain.Exceptions;
using CheckDrive.Domain.Interfaces;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace CheckDrive.Application.Services.Review;
@@ -13,11 +15,16 @@ internal sealed class OperatorReviewService : IOperatorReviewService
{
private readonly ICheckDriveDbContext _context;
private readonly IMapper _mapper;
+ private readonly IHubContext _hubContext;
- public OperatorReviewService(ICheckDriveDbContext context, IMapper mapper)
+ public OperatorReviewService(
+ ICheckDriveDbContext context,
+ IMapper mapper,
+ IHubContext hubContext)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
}
public async Task CreateAsync(CreateOperatorReviewDto review)
@@ -27,16 +34,30 @@ public async Task CreateAsync(CreateOperatorReviewDto review)
var checkPoint = await GetAndValidateCheckPointAsync(review.CheckPointId);
var @operator = await GetAndValidateOperatorAsync(review.ReviewerId);
var oilMark = await GetAndValidateOilMarkAsync(review.OilMarkId);
+ var car = checkPoint.MechanicHandover!.Car;
- RefillCar(checkPoint.MechanicHandover!.Car, review);
- UpdateCheckPoint(checkPoint, review);
+ if (car.FuelCapacity < review.InitialOilAmount + review.OilRefillAmount)
+ {
+ throw new InvalidOperationException(
+ $"Oil refill amount ({review.InitialOilAmount + review.OilRefillAmount}) exceeds Car's fuel capacity ({car.FuelCapacity}).");
+ }
+
+ if (!review.IsApprovedByReviewer)
+ {
+ checkPoint.Stage = CheckPointStage.OperatorReview;
+ checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
+ }
- var reviewEntity = CreateReviewEntity(checkPoint, oilMark, @operator, review);
+ var reviewEntity = CreateReview(checkPoint, oilMark, @operator, review);
- var createdReview = _context.OperatorReviews.Add(reviewEntity).Entity;
+ _context.OperatorReviews.Add(reviewEntity);
await _context.SaveChangesAsync();
- var dto = _mapper.Map(createdReview);
+ var dto = _mapper.Map(reviewEntity);
+
+ await _hubContext.Clients
+ .User(dto.DriverId.ToString())
+ .OperatorReviewConfirmation(dto);
return dto;
}
@@ -44,9 +65,10 @@ public async Task CreateAsync(CreateOperatorReviewDto review)
private async Task GetAndValidateCheckPointAsync(int checkPointId)
{
var checkPoint = await _context.CheckPoints
- .AsTracking()
+ .Include(x => x.DoctorReview)
+ .ThenInclude(x => x.Driver)
.Include(x => x.MechanicHandover)
- .ThenInclude(mh => mh!.Car)
+ .ThenInclude(x => x!.Car)
.FirstOrDefaultAsync(x => x.Id == checkPointId);
if (checkPoint is null)
@@ -93,39 +115,7 @@ private async Task GetAndValidateOilMarkAsync(int oilMarkId)
return oilMark;
}
- private static void RefillCar(Car car, CreateOperatorReviewDto review)
- {
- ArgumentNullException.ThrowIfNull(car);
-
- if (!review.IsApprovedByReviewer)
- {
- return;
- }
-
- var total = review.InitialOilAmount + review.OilRefillAmount;
-
- if (car.FuelCapacity < total)
- {
- throw new FuelAmountExceedsCarCapacityException($"{total} exceeds car fuel capacity: {car.FuelCapacity}.");
- }
-
- car.RemainingFuel = total;
- }
-
- private static void UpdateCheckPoint(CheckPoint checkPoint, CreateOperatorReviewDto review)
- {
- ArgumentNullException.ThrowIfNull(checkPoint);
- ArgumentNullException.ThrowIfNull(review);
-
- checkPoint.Stage = CheckPointStage.OperatorReview;
-
- if (!review.IsApprovedByReviewer)
- {
- checkPoint.Status = CheckPointStatus.InterruptedByReviewerRejection;
- }
- }
-
- private static OperatorReview CreateReviewEntity(
+ private static OperatorReview CreateReview(
CheckPoint checkPoint,
OilMark oilMark,
Operator @operator,
diff --git a/CheckDrive.Api/CheckDrive.Application/Services/ReviewHistoryService.cs b/CheckDrive.Api/CheckDrive.Application/Services/ReviewHistoryService.cs
new file mode 100644
index 00000000..8387484e
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Services/ReviewHistoryService.cs
@@ -0,0 +1,131 @@
+using AutoMapper;
+using CheckDrive.Application.DTOs.CheckPoint;
+using CheckDrive.Application.DTOs.DoctorReview;
+using CheckDrive.Application.DTOs.OperatorReview;
+using CheckDrive.Application.DTOs.Review;
+using CheckDrive.Application.Interfaces.Review;
+using CheckDrive.Domain.Enums;
+using CheckDrive.Domain.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace CheckDrive.Application.Services;
+
+internal sealed class ReviewHistoryService : IReviewHistoryService
+{
+ private readonly ICheckDriveDbContext _context;
+ private readonly IMapper _mapper;
+
+ public ReviewHistoryService(ICheckDriveDbContext context, IMapper mapper)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ }
+
+ public async Task> GetDriverHistoriesAsync(int driverId)
+ {
+ var checkPoints = await _context.CheckPoints
+ .AsNoTracking()
+ .Where(x => x.DoctorReview.DriverId == driverId)
+ .Where(x => x.Status == CheckPointStatus.Completed || x.Status == CheckPointStatus.AutomaticallyClosed)
+ .Include(x => x.DoctorReview)
+ .ThenInclude(x => x.Doctor)
+ .Include(x => x.DoctorReview)
+ .ThenInclude(x => x.Driver)
+ .Include(x => x.MechanicHandover)
+ .ThenInclude(x => x.Mechanic)
+ .Include(x => x.MechanicHandover)
+ .ThenInclude(x => x.Car)
+ .Include(x => x.OperatorReview)
+ .ThenInclude(x => x.Operator)
+ .Include(x => x.OperatorReview)
+ .ThenInclude(x => x.OilMark)
+ .Include(x => x.MechanicAcceptance)
+ .ThenInclude(x => x.Mechanic)
+ .Include(x => x.DispatcherReview)
+ .Include(x => x.ManagerReview)
+ .Include(x => x.Debt)
+ .AsSplitQuery()
+ .ToListAsync();
+
+ var dtos = _mapper.Map>(checkPoints);
+
+ return dtos;
+
+ }
+
+ public async Task> GetDoctorHistoriesAsync(int doctorId)
+ {
+ var reviews = await _context.DoctorReviews
+ .Include(x => x.Driver)
+ .Where(x => x.DoctorId == doctorId)
+ .ToListAsync();
+
+ var dtos = _mapper.Map>(reviews);
+
+ return dtos;
+ }
+
+ public async Task> GetMechanicHistoriesAsync(int mechanicId)
+ {
+ var handovers = await _context.MechanicHandovers
+ .AsNoTracking()
+ .Where(x => x.MechanicId == mechanicId)
+ .Include(x => x.CheckPoint)
+ .ThenInclude(x => x.DoctorReview)
+ .ThenInclude(x => x.Driver)
+ .Select(x => new MechanicReviewHistoryDto(
+ x.Id,
+ x.CheckPoint.DoctorReview.DriverId,
+ x.CheckPoint.DoctorReview.Driver.FirstName + " " + x.CheckPoint.DoctorReview.Driver.LastName,
+ x.Notes,
+ x.Date,
+ x.Status))
+ .ToListAsync();
+ var acceptances = await _context.MechanicAcceptances
+ .Include(x => x.CheckPoint)
+ .ThenInclude(x => x.DoctorReview)
+ .ThenInclude(x => x.Driver)
+ .Select(x => new MechanicReviewHistoryDto(
+ x.Id,
+ x.CheckPoint.DoctorReview.DriverId,
+ x.CheckPoint.DoctorReview.Driver.FirstName + " " + x.CheckPoint.DoctorReview.Driver.LastName,
+ x.Notes,
+ x.Date,
+ x.Status))
+ .ToListAsync();
+
+ List allReviews = [.. handovers, .. acceptances];
+ var orderedReviews = allReviews.OrderByDescending(x => x.Date).ToList();
+
+ return orderedReviews;
+ }
+
+ public async Task> GetOperatorHistoriesAsync(int operatorId)
+ {
+ var reviews = await _context.OperatorReviews
+ .AsNoTracking()
+ .Where(x => x.OperatorId == operatorId)
+ .Include(x => x.Operator)
+ .Include(x => x.OilMark)
+ .Include(x => x.CheckPoint)
+ .ThenInclude(x => x.DoctorReview)
+ .ThenInclude(x => x.Driver)
+ .AsSplitQuery()
+ .Select(x => new OperatorReviewDto(
+ x.CheckPointId,
+ x.OperatorId,
+ x.Operator.FirstName + " " + x.Operator.LastName,
+ x.CheckPoint.DoctorReview.DriverId,
+ x.CheckPoint.DoctorReview.Driver.FirstName + " " + x.CheckPoint.DoctorReview.Driver.LastName,
+ x.OilMarkId,
+ x.OilMark.Name,
+ x.Notes,
+ x.Date,
+ x.Status,
+ x.InitialOilAmount,
+ x.OilRefillAmount))
+ .ToListAsync();
+
+ return reviews;
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Validators/Car/CreateCarValidator.cs b/CheckDrive.Api/CheckDrive.Application/Validators/Car/CreateCarValidator.cs
new file mode 100644
index 00000000..298376f4
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Validators/Car/CreateCarValidator.cs
@@ -0,0 +1,72 @@
+using CheckDrive.Application.DTOs.Car;
+using FluentValidation;
+
+namespace CheckDrive.Application.Validators.Car;
+
+public sealed class CreateCarValidator : AbstractValidator
+{
+ public CreateCarValidator()
+ {
+ RuleFor(x => x.Model)
+ .NotEmpty()
+ .WithMessage("Car model must be specified.");
+
+ RuleFor(x => x.Number)
+ .NotEmpty()
+ .WithMessage("Car number must be specified.");
+
+ RuleFor(x => x.ManufacturedYear)
+ .GreaterThan(1900)
+ .LessThan(2026)
+ .WithMessage(x => $"Invalid manufactured year: {x.ManufacturedYear}.");
+
+ RuleFor(x => x.Mileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid mileage valule {x.Mileage}.");
+
+ RuleFor(x => x.CurrentMonthMileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current month mileage: {x.CurrentMonthMileage}.");
+
+ RuleFor(x => x.CurrentYearMileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current year mileage: {x.CurrentYearMileage}.");
+
+ RuleFor(x => x.MonthlyDistanceLimit)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid monthly distance limit: {x.MonthlyDistanceLimit}.");
+
+ RuleFor(x => x.YearlyDistanceLimit)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid yearly distance limit: {x.YearlyDistanceLimit}.");
+
+ RuleFor(x => x.CurrentMonthFuelConsumption)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current month fuel consumption: {x.CurrentMonthFuelConsumption}.");
+
+ RuleFor(x => x.CurrentYearMileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current year fuel consumption: {x.CurrentYearFuelConsumption}.");
+
+ RuleFor(x => x.MonthlyFuelConsumptionLimit)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid monthly fuel consumption limit: {x.MonthlyFuelConsumptionLimit}.");
+
+ RuleFor(x => x.YearlyFuelConsumptionLimit)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid yearly fuel consumption limit: {x.YearlyFuelConsumptionLimit}.");
+
+ RuleFor(x => x.AverageFuelConsumption)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid average fuel consumption value: {x.AverageFuelConsumption}.");
+
+ RuleFor(x => x.FuelCapacity)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid fuel capacity value: {x.FuelCapacity}.");
+
+ RuleFor(x => x.RemainingFuel)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid remaining fuel value: {x.RemainingFuel}.");
+
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Validators/Car/UpdateCarValidator.cs b/CheckDrive.Api/CheckDrive.Application/Validators/Car/UpdateCarValidator.cs
new file mode 100644
index 00000000..9c481081
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Validators/Car/UpdateCarValidator.cs
@@ -0,0 +1,75 @@
+using CheckDrive.Application.DTOs.Car;
+using FluentValidation;
+
+namespace CheckDrive.Application.Validators.Car;
+
+public sealed class UpdateCarValidator : AbstractValidator
+{
+ public UpdateCarValidator()
+ {
+ RuleFor(x => x.Id)
+ .GreaterThanOrEqualTo(1)
+ .WithMessage(x => $"Invalid car id: {x.Id}.");
+
+ RuleFor(x => x.Model)
+ .NotEmpty()
+ .WithMessage("Car model must be specified.");
+
+ RuleFor(x => x.Number)
+ .NotEmpty()
+ .WithMessage("Car number must be specified.");
+
+ RuleFor(x => x.ManufacturedYear)
+ .GreaterThan(1900)
+ .LessThan(2026)
+ .WithMessage(x => $"Invalid manufactured year: {x.ManufacturedYear}.");
+
+ RuleFor(x => x.Mileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid mileage valule {x.Mileage}.");
+
+ RuleFor(x => x.CurrentMonthMileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current month mileage: {x.CurrentMonthMileage}.");
+
+ RuleFor(x => x.CurrentYearMileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current year mileage: {x.CurrentYearMileage}.");
+
+ RuleFor(x => x.MonthlyDistanceLimit)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid monthly distance limit: {x.MonthlyDistanceLimit}.");
+
+ RuleFor(x => x.YearlyDistanceLimit)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid yearly distance limit: {x.YearlyDistanceLimit}.");
+
+ RuleFor(x => x.CurrentMonthFuelConsumption)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current month fuel consumption: {x.CurrentMonthFuelConsumption}.");
+
+ RuleFor(x => x.CurrentYearMileage)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid current year fuel consumption: {x.CurrentYearFuelConsumption}.");
+
+ RuleFor(x => x.MonthlyFuelConsumptionLimit)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid monthly fuel consumption limit: {x.MonthlyFuelConsumptionLimit}.");
+
+ RuleFor(x => x.YearlyFuelConsumptionLimit)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid yearly fuel consumption limit: {x.YearlyFuelConsumptionLimit}.");
+
+ RuleFor(x => x.AverageFuelConsumption)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid average fuel consumption value: {x.AverageFuelConsumption}.");
+
+ RuleFor(x => x.FuelCapacity)
+ .GreaterThan(0)
+ .WithMessage(x => $"Invalid fuel capacity value: {x.FuelCapacity}.");
+
+ RuleFor(x => x.RemainingFuel)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage(x => $"Invalid remaining fuel value: {x.RemainingFuel}.");
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Validators/OilMark/CreateOilMarkValidator.cs b/CheckDrive.Api/CheckDrive.Application/Validators/OilMark/CreateOilMarkValidator.cs
new file mode 100644
index 00000000..2435c6d0
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Validators/OilMark/CreateOilMarkValidator.cs
@@ -0,0 +1,14 @@
+using FluentValidation;
+using CheckDrive.Application.DTOs.OilMark;
+
+namespace CheckDrive.Application.Validators.OilMark;
+
+public sealed class CreateOilMarkValidator : AbstractValidator
+{
+ public CreateOilMarkValidator()
+ {
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithMessage("Oil mark should be specified.");
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Application/Validators/OilMark/UpdateOilMarkValidator.cs b/CheckDrive.Api/CheckDrive.Application/Validators/OilMark/UpdateOilMarkValidator.cs
new file mode 100644
index 00000000..636b9826
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Application/Validators/OilMark/UpdateOilMarkValidator.cs
@@ -0,0 +1,18 @@
+using FluentValidation;
+using CheckDrive.Application.DTOs.OilMark;
+
+namespace CheckDrive.Application.Validators.OilMark;
+
+public sealed class UpdateOilMarkValidator : AbstractValidator
+{
+ public UpdateOilMarkValidator()
+ {
+ RuleFor(x => x.Id)
+ .GreaterThanOrEqualTo(1)
+ .WithMessage(x => $"Invalid oil mark id: {x.Id}.");
+
+ RuleFor(x => x.Name)
+ .NotEmpty()
+ .WithMessage("Oil mark name should be specified.");
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Entities/Car.cs b/CheckDrive.Api/CheckDrive.Domain/Entities/Car.cs
index 2b5d54dd..110f0845 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Entities/Car.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Entities/Car.cs
@@ -3,6 +3,8 @@
namespace CheckDrive.Domain.Entities;
+// TODO: Add proper validation for the car with new properties introduced,
+// such as monthly, yearly distance and fuel consumption limits
public class Car : EntityBase
{
public string Model { get; set; }
@@ -10,7 +12,14 @@ public class Car : EntityBase
public string Number { get; set; }
public int ManufacturedYear { get; set; }
public int Mileage { get; set; }
+ public int CurrentMonthMileage { get; set; }
+ public int CurrentYearMileage { get; set; }
+ public int MonthlyDistanceLimit { get; set; }
public int YearlyDistanceLimit { get; set; }
+ public decimal CurrentMonthFuelConsumption { get; set; }
+ public decimal CurrentYearFuelConsumption { get; set; }
+ public decimal MonthlyFuelConsumptionLimit { get; set; }
+ public decimal YearlyFuelConsumptionLimit { get; set; }
public decimal AverageFuelConsumption { get; set; }
public decimal FuelCapacity { get; set; }
public decimal RemainingFuel { get; set; }
diff --git a/CheckDrive.Api/CheckDrive.Domain/Entities/CheckPoint.cs b/CheckDrive.Api/CheckDrive.Domain/Entities/CheckPoint.cs
index 443380e9..bdaddc9e 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Entities/CheckPoint.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Entities/CheckPoint.cs
@@ -15,5 +15,6 @@ public class CheckPoint : EntityBase
public virtual OperatorReview? OperatorReview { get; set; }
public virtual MechanicAcceptance? MechanicAcceptance { get; set; }
public virtual DispatcherReview? DispatcherReview { get; set; }
+ public virtual ManagerReview? ManagerReview { get; set; }
public virtual Debt? Debt { get; set; }
}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Entities/Debt.cs b/CheckDrive.Api/CheckDrive.Domain/Entities/Debt.cs
index c84cb520..8bde6224 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Entities/Debt.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Entities/Debt.cs
@@ -11,4 +11,7 @@ public class Debt : EntityBase
public int CheckPointId { get; set; }
public required virtual CheckPoint CheckPoint { get; set; }
+
+ public int? ManagerReviewId { get; set; }
+ public virtual ManagerReview? ManagerReview { get; set; }
}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Entities/DispatcherReview.cs b/CheckDrive.Api/CheckDrive.Domain/Entities/DispatcherReview.cs
index affa3ab3..789392af 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Entities/DispatcherReview.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Entities/DispatcherReview.cs
@@ -5,7 +5,7 @@ namespace CheckDrive.Domain.Entities;
public class DispatcherReview : ReviewBase
{
public decimal? FuelConsumptionAdjustment { get; set; }
- public decimal? DistanceTravelledAdjustment { get; set; }
+ public decimal? FinalMileageAdjustment { get; set; }
public int CheckPointId { get; set; }
public required virtual CheckPoint CheckPoint { get; set; }
diff --git a/CheckDrive.Api/CheckDrive.Domain/Entities/Manager.cs b/CheckDrive.Api/CheckDrive.Domain/Entities/Manager.cs
new file mode 100644
index 00000000..01d0670c
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Domain/Entities/Manager.cs
@@ -0,0 +1,11 @@
+namespace CheckDrive.Domain.Entities;
+
+public class Manager : Employee
+{
+ public virtual ICollection Reviews { get; set; }
+
+ public Manager()
+ {
+ Reviews = new HashSet();
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Entities/ManagerReview.cs b/CheckDrive.Api/CheckDrive.Domain/Entities/ManagerReview.cs
new file mode 100644
index 00000000..6b6632d6
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Domain/Entities/ManagerReview.cs
@@ -0,0 +1,18 @@
+using CheckDrive.Domain.Common;
+
+namespace CheckDrive.Domain.Entities;
+
+public class ManagerReview : ReviewBase
+{
+ public decimal? DebtAmountAdjusment { get; set; }
+ public decimal? FuelConsumptionAdjustment { get; set; }
+
+ public int CheckPointId { get; set; }
+ public required virtual CheckPoint CheckPoint { get; set; }
+
+ public int ManagerId { get; set; }
+ public required virtual Manager Manager { get; set; }
+
+ public int? DebtId { get; set; }
+ public virtual Debt? Debt { get; set; }
+}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Enums/CheckPointStatus.cs b/CheckDrive.Api/CheckDrive.Domain/Enums/CheckPointStatus.cs
index 475e27fe..c61c615b 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Enums/CheckPointStatus.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Enums/CheckPointStatus.cs
@@ -8,4 +8,5 @@ public enum CheckPointStatus
InterruptedByDriverRejection = 3,
AutomaticallyClosed = 4,
PendingManagerReview = 5,
+ ClosedByManager = 6,
}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Enums/DebtStatus.cs b/CheckDrive.Api/CheckDrive.Domain/Enums/DebtStatus.cs
index 8bba2958..b1ba199f 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Enums/DebtStatus.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Enums/DebtStatus.cs
@@ -4,5 +4,6 @@ public enum DebtStatus
{
Paid,
Unpaid,
- PartiallyPaid
+ PartiallyPaid,
+ ClosedByManager
}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Enums/ReviewType.cs b/CheckDrive.Api/CheckDrive.Domain/Enums/ReviewType.cs
index a9c8217b..ff045bd3 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Enums/ReviewType.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Enums/ReviewType.cs
@@ -2,9 +2,9 @@
public enum ReviewType
{
- Doctor,
+ DoctorReview,
MechanicHandover,
- Operator,
+ OperatorReview,
MechanicAcceptance,
- Dispatcher
+ DispatcherReview
}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Exceptions/CarUnavailableException.cs b/CheckDrive.Api/CheckDrive.Domain/Exceptions/CarUnavailableException.cs
new file mode 100644
index 00000000..b653e37a
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Domain/Exceptions/CarUnavailableException.cs
@@ -0,0 +1,7 @@
+namespace CheckDrive.Domain.Exceptions;
+
+public sealed class CarUnavailableException : Exception
+{
+ public CarUnavailableException() : base() { }
+ public CarUnavailableException(string message) : base(message) { }
+}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Exceptions/UnavailableCarException.cs b/CheckDrive.Api/CheckDrive.Domain/Exceptions/UnavailableCarException.cs
deleted file mode 100644
index 803954ab..00000000
--- a/CheckDrive.Api/CheckDrive.Domain/Exceptions/UnavailableCarException.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace CheckDrive.Domain.Exceptions;
-
-public sealed class UnavailableCarException : Exception
-{
- public UnavailableCarException() : base() { }
- public UnavailableCarException(string message) : base(message) { }
-}
diff --git a/CheckDrive.Api/CheckDrive.Domain/Interfaces/ICheckDriveDbContext.cs b/CheckDrive.Api/CheckDrive.Domain/Interfaces/ICheckDriveDbContext.cs
index e2b4f01b..ca7c15b3 100644
--- a/CheckDrive.Api/CheckDrive.Domain/Interfaces/ICheckDriveDbContext.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/Interfaces/ICheckDriveDbContext.cs
@@ -13,6 +13,7 @@ public interface ICheckDriveDbContext
DbSet Mechanics { get; set; }
DbSet Operators { get; set; }
DbSet Dispatchers { get; set; }
+ DbSet Managers { get; set; }
DbSet Cars { get; set; }
DbSet CheckPoints { get; set; }
DbSet DoctorReviews { get; set; }
@@ -20,6 +21,7 @@ public interface ICheckDriveDbContext
DbSet OperatorReviews { get; set; }
DbSet MechanicAcceptances { get; set; }
DbSet DispatcherReviews { get; set; }
+ DbSet ManagerReviews { get; set; }
DbSet Debts { get; set; }
DbSet OilMarks { get; set; }
diff --git a/CheckDrive.Api/CheckDrive.Domain/QueryParameters/CheckPointQueryParameters.cs b/CheckDrive.Api/CheckDrive.Domain/QueryParameters/CheckPointQueryParameters.cs
index accbcbac..21730521 100644
--- a/CheckDrive.Api/CheckDrive.Domain/QueryParameters/CheckPointQueryParameters.cs
+++ b/CheckDrive.Api/CheckDrive.Domain/QueryParameters/CheckPointQueryParameters.cs
@@ -6,7 +6,7 @@ namespace CheckDrive.Domain.QueryParameters;
public class CheckPointQueryParameters : QueryParametersBase
{
public int? DriverId { get; set; }
- public CheckPointStatus? Status { get; set; }
public CheckPointStage? Stage { get; set; }
- public DateFilter? DateFilter { get; set; }
+ public CheckPointStatus? Status { get; set; } = CheckPointStatus.InProgress;
+ public DateFilter? Date { get; set; }
}
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Configurations/JwtOptions.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Configurations/JwtOptions.cs
index 11e8c240..b81e2659 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Configurations/JwtOptions.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Configurations/JwtOptions.cs
@@ -6,7 +6,10 @@ public class JwtOptions
{
public const string SectionName = nameof(JwtOptions);
- [Required(ErrorMessage = "Secret Key is required")]
+ [Required(ErrorMessage = "Secret Key is required.")]
public required string SecretKey { get; init; }
- public int ExpiresHours { get; init; }
+
+ [Required(ErrorMessage = "Expiration time is required.")]
+ [Range(1, int.MaxValue, ErrorMessage = "Expiration time must be greater than 1 hour.")]
+ public int ExpiresInHours { get; init; }
}
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Helpers/JwtTokenGenerator.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Helpers/JwtTokenGenerator.cs
index bc7e94df..4e256e9a 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Helpers/JwtTokenGenerator.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Helpers/JwtTokenGenerator.cs
@@ -1,6 +1,6 @@
using CheckDrive.Application.Interfaces.Auth;
+using CheckDrive.Domain.Entities;
using CheckDrive.Infrastructure.Configurations;
-using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
@@ -18,15 +18,14 @@ public JwtTokenGenerator(IOptions options)
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
- public string GenerateToken(IdentityUser user,
- IList roles)
+ public string GenerateToken(Employee employee, IList roles)
{
- var claims = GetClaims(user, roles);
+ var claims = GetClaims(employee, roles);
var signingKey = GetSigningKey();
var securityToken = new JwtSecurityToken(
claims: claims,
- expires: DateTime.UtcNow.AddHours(_options.ExpiresHours),
+ expires: DateTime.UtcNow.AddHours(_options.ExpiresInHours),
signingCredentials: signingKey);
var token = new JwtSecurityTokenHandler().WriteToken(securityToken);
@@ -42,11 +41,12 @@ private SigningCredentials GetSigningKey()
return signingKey;
}
- private static List GetClaims(IdentityUser user, IList roles)
+ private static List GetClaims(Employee employee, IList roles)
{
var claims = new List()
{
- new (ClaimTypes.NameIdentifier, user.Id.ToString()),
+ new (ClaimTypes.PrimarySid, employee.AccountId),
+ new (ClaimTypes.NameIdentifier, employee.Id.ToString()),
};
foreach (var role in roles)
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/CheckDriveDbContext.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/CheckDriveDbContext.cs
index 1d993af8..1702ca19 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/CheckDriveDbContext.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/CheckDriveDbContext.cs
@@ -16,6 +16,7 @@ public class CheckDriveDbContext : IdentityDbContext, ICheckDriveDbContext
public virtual DbSet Mechanics { get; set; }
public virtual DbSet Operators { get; set; }
public virtual DbSet Dispatchers { get; set; }
+ public virtual DbSet Managers { get; set; }
public virtual DbSet Cars { get; set; }
public virtual DbSet CheckPoints { get; set; }
public virtual DbSet DoctorReviews { get; set; }
@@ -23,6 +24,7 @@ public class CheckDriveDbContext : IdentityDbContext, ICheckDriveDbContext
public virtual DbSet OperatorReviews { get; set; }
public virtual DbSet MechanicAcceptances { get; set; }
public virtual DbSet DispatcherReviews { get; set; }
+ public virtual DbSet ManagerReviews { get; set; }
public virtual DbSet Debts { get; set; }
public virtual DbSet OilMarks { get; set; }
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/CarConfiguration.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/CarConfiguration.cs
index 4736ccb1..80c88d05 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/CarConfiguration.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/CarConfiguration.cs
@@ -14,8 +14,7 @@ public void Configure(EntityTypeBuilder builder)
#region Relationships
- builder
- .HasMany(c => c.Handovers)
+ builder.HasMany(c => c.Handovers)
.WithOne(ch => ch.Car)
.HasForeignKey(ch => ch.CarId);
@@ -24,41 +23,58 @@ public void Configure(EntityTypeBuilder builder)
#region Properties
builder.Property(c => c.Model)
- .HasMaxLength(Constants.MAX_STRING_LENGTH)
- .IsRequired();
+ .HasMaxLength(Constants.MAX_STRING_LENGTH)
+ .IsRequired();
builder.Property(c => c.Color)
- .HasMaxLength(Constants.MAX_STRING_LENGTH)
- .IsRequired();
+ .HasMaxLength(Constants.MAX_STRING_LENGTH)
+ .IsRequired();
builder.Property(c => c.Number)
- .HasMaxLength(Constants.CAR_NUMBER_LENGTH)
- .IsRequired();
+ .HasMaxLength(Constants.CAR_NUMBER_LENGTH)
+ .IsRequired();
builder.Property(c => c.ManufacturedYear)
- .IsRequired();
+ .IsRequired();
builder.Property(c => c.Mileage)
- .IsRequired();
+ .IsRequired();
+
+ builder.Property(c => c.CurrentMonthMileage)
+ .IsRequired();
+
+ builder.Property(c => c.CurrentYearMileage)
+ .IsRequired();
+
+ builder.Property(c => c.MonthlyDistanceLimit)
+ .IsRequired();
builder.Property(c => c.YearlyDistanceLimit)
- .IsRequired();
+ .IsRequired();
+
+ builder.Property(c => c.MonthlyFuelConsumptionLimit)
+ .HasPrecision(18, 2)
+ .IsRequired();
+
+ builder.Property(c => c.YearlyFuelConsumptionLimit)
+ .HasPrecision(18, 2)
+ .IsRequired();
builder.Property(c => c.AverageFuelConsumption)
- .HasPrecision(18, 2)
- .IsRequired();
+ .HasPrecision(18, 2)
+ .IsRequired();
builder.Property(c => c.FuelCapacity)
- .HasPrecision(18, 2)
- .IsRequired();
+ .HasPrecision(18, 2)
+ .IsRequired();
builder.Property(c => c.RemainingFuel)
- .HasPrecision(18, 2)
- .IsRequired();
+ .HasPrecision(18, 2)
+ .IsRequired();
builder.Property(c => c.Status)
- .HasDefaultValue(CarStatus.Free)
- .IsRequired();
+ .HasDefaultValue(CarStatus.Free)
+ .IsRequired();
#endregion
}
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/DispatcherReviewConfiguration.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/DispatcherReviewConfiguration.cs
index 52a96ee0..e065ce47 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/DispatcherReviewConfiguration.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/DispatcherReviewConfiguration.cs
@@ -37,7 +37,7 @@ public void Configure(EntityTypeBuilder builder)
.IsRequired(false);
builder
- .Property(dr => dr.DistanceTravelledAdjustment)
+ .Property(dr => dr.FinalMileageAdjustment)
.HasPrecision(18, 2)
.IsRequired(false);
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/EmployeeConfiguration.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/EmployeeConfiguration.cs
index 74bd5c6a..7f541735 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/EmployeeConfiguration.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/EmployeeConfiguration.cs
@@ -31,7 +31,8 @@ public void Configure(EntityTypeBuilder builder)
.HasValue(EmployeePosition.Doctor)
.HasValue(EmployeePosition.Mechanic)
.HasValue(EmployeePosition.Operator)
- .HasValue(EmployeePosition.Dispatcher);
+ .HasValue(EmployeePosition.Dispatcher)
+ .HasValue(EmployeePosition.Manager);
builder
.Property(e => e.FirstName)
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/ManagerReviewConfigurations.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/ManagerReviewConfigurations.cs
new file mode 100644
index 00000000..62d6bc9d
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/ManagerReviewConfigurations.cs
@@ -0,0 +1,53 @@
+using CheckDrive.Domain.Entities;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore;
+
+namespace CheckDrive.Infrastructure.Persistence.Configurations;
+
+internal sealed class ManagerReviewConfigurations : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable(nameof(ManagerReview));
+ builder.HasKey(mr => mr.Id);
+
+ #region Relationships
+
+ builder
+ .HasOne(mr => mr.CheckPoint)
+ .WithOne(cp => cp.ManagerReview)
+ .HasForeignKey(mr => mr.CheckPointId)
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ builder
+ .HasOne(mr => mr.Manager)
+ .WithMany(m => m.Reviews)
+ .HasForeignKey(mr => mr.ManagerId)
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ builder
+ .HasOne(mr => mr.Debt)
+ .WithOne(d => d.ManagerReview)
+ .HasForeignKey(mr => mr.ManagerId)
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ #endregion
+
+ #region Properties
+
+ builder
+ .Property(mr => mr.DebtAmountAdjusment)
+ .HasPrecision(18, 2)
+ .IsRequired(false);
+
+ builder
+ .Property(mr => mr.FuelConsumptionAdjustment)
+ .HasPrecision(18, 2)
+ .IsRequired(false);
+
+ #endregion
+ }
+}
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/OilMarkConfiguration.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/OilMarkConfiguration.cs
index 66ab6a90..55512c35 100644
--- a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/OilMarkConfiguration.cs
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Configurations/OilMarkConfiguration.cs
@@ -30,5 +30,36 @@ public void Configure(EntityTypeBuilder builder)
.IsRequired();
#endregion
+
+ #region Default Data
+
+ builder.HasData(
+ new OilMark
+ {
+ Id = 1,
+ Name = "80"
+ },
+ new OilMark
+ {
+ Id = 2,
+ Name = "85"
+ },
+ new OilMark
+ {
+ Id = 3,
+ Name = "90"
+ },
+ new OilMark
+ {
+ Id = 4,
+ Name = "95"
+ },
+ new OilMark
+ {
+ Id = 5,
+ Name = "100"
+ });
+
+ #endregion
}
}
diff --git a/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Migrations/20241006103314_Add_Manager_ManagerReview.Designer.cs b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Migrations/20241006103314_Add_Manager_ManagerReview.Designer.cs
new file mode 100644
index 00000000..76a28fdb
--- /dev/null
+++ b/CheckDrive.Api/CheckDrive.Infrastructure/Persistence/Migrations/20241006103314_Add_Manager_ManagerReview.Designer.cs
@@ -0,0 +1,1036 @@
+//
+using System;
+using CheckDrive.Infrastructure.Persistence;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace CheckDrive.Infrastructure.Persistence.Migrations
+{
+ [DbContext(typeof(CheckDriveDbContext))]
+ [Migration("20241006103314_Add_Manager_ManagerReview")]
+ partial class Add_Manager_ManagerReview
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("CheckDrive.Domain.Entities.Car", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AverageFuelConsumption")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Color")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("FuelCapacity")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("ManufacturedYear")
+ .HasColumnType("int");
+
+ b.Property("Mileage")
+ .HasColumnType("int");
+
+ b.Property