add vehicle enrollments management
This commit is contained in:
parent
3ebd0c3a2c
commit
5ee8c9c5df
@ -0,0 +1,7 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Common.Interfaces
|
||||
.Persistence.Repositories;
|
||||
|
||||
public interface RouteAddressRepository :
|
||||
BaseRepository<RouteAddress> { }
|
@ -0,0 +1,7 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Common.Interfaces
|
||||
.Persistence.Repositories;
|
||||
|
||||
public interface VehicleEnrollmentRepository :
|
||||
BaseRepository<VehicleEnrollment> { }
|
@ -22,6 +22,10 @@ public interface UnitOfWork : IDisposable
|
||||
|
||||
TrainRepository TrainRepository { get; }
|
||||
|
||||
VehicleEnrollmentRepository VehicleEnrollmentRepository { get; }
|
||||
|
||||
RouteAddressRepository RouteAddressRepository { get; }
|
||||
|
||||
int Save();
|
||||
|
||||
Task<int> SaveAsync(CancellationToken cancellationToken);
|
||||
|
@ -8,7 +8,15 @@
|
||||
},
|
||||
"Validation": {
|
||||
"DistinctOrder": "Must have distinct order values.",
|
||||
"SameVehicleType": "Must have the same vehicle type."
|
||||
"SameVehicleType": "Must have the same vehicle type.",
|
||||
"DateTimeOffset": {
|
||||
"GreaterThanOrEqualTo": "Must be greater or equal to {0:U}"
|
||||
},
|
||||
"VehicleEnrollments": {
|
||||
"OverlapWithOther": "Provided vehicle enrollment overlapping in schedule with other one.",
|
||||
"NegativeTime": "Specified time must be positive time span.",
|
||||
"NegativeCost": "Specified cost must be positive value."
|
||||
}
|
||||
},
|
||||
"ExceptionHandling": {
|
||||
"ValidationException": {
|
||||
|
@ -5,18 +5,20 @@ namespace cuqmbr.TravelGuide.Application.Routes;
|
||||
|
||||
public sealed class RouteAddressDto : IMapFrom<RouteAddress>
|
||||
{
|
||||
public Guid RouteAddressUuid { get; set; }
|
||||
|
||||
public short Order { get; set; }
|
||||
|
||||
|
||||
public Guid Uuid { get; set; }
|
||||
public Guid AddressUuid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string AddressName { get; set; }
|
||||
|
||||
public double Longitude { get; set; }
|
||||
public double AddressLongitude { get; set; }
|
||||
|
||||
public double Latitude { get; set; }
|
||||
public double AddressLatitude { get; set; }
|
||||
|
||||
public string VehicleType { get; set; }
|
||||
public string AddressVehicleType { get; set; }
|
||||
|
||||
public Guid CountryUuid { get; set; }
|
||||
|
||||
@ -34,19 +36,22 @@ public sealed class RouteAddressDto : IMapFrom<RouteAddress>
|
||||
{
|
||||
profile.CreateMap<RouteAddress, RouteAddressDto>()
|
||||
.ForMember(
|
||||
d => d.Uuid,
|
||||
d => d.RouteAddressUuid,
|
||||
opt => opt.MapFrom(s => s.Guid))
|
||||
.ForMember(
|
||||
d => d.AddressUuid,
|
||||
opt => opt.MapFrom(s => s.Address.Guid))
|
||||
.ForMember(
|
||||
d => d.Name,
|
||||
d => d.AddressName,
|
||||
opt => opt.MapFrom(s => s.Address.Name))
|
||||
.ForMember(
|
||||
d => d.Longitude,
|
||||
d => d.AddressLongitude,
|
||||
opt => opt.MapFrom(s => s.Address.Longitude))
|
||||
.ForMember(
|
||||
d => d.Latitude,
|
||||
d => d.AddressLatitude,
|
||||
opt => opt.MapFrom(s => s.Address.Latitude))
|
||||
.ForMember(
|
||||
d => d.VehicleType,
|
||||
d => d.AddressVehicleType,
|
||||
opt => opt.MapFrom(s => s.Address.VehicleType.Name))
|
||||
.ForMember(
|
||||
d => d.CityUuid,
|
||||
|
@ -0,0 +1,20 @@
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments.Models;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.AddVehicleEnrollment;
|
||||
|
||||
public record AddVehicleEnrollmentCommand : IRequest<VehicleEnrollmentDto>
|
||||
{
|
||||
public DateTimeOffset DepartureTime { get; set; }
|
||||
|
||||
public Currency Currency { get; set; }
|
||||
|
||||
|
||||
public Guid VehicleGuid { get; set; }
|
||||
|
||||
public Guid RouteGuid { get; set; }
|
||||
|
||||
public ICollection<RouteAddressDetailModel> RouteAddressDetails { get; set; }
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.AddVehicleEnrollment;
|
||||
|
||||
public class AddVehicleEnrollmentCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<AddVehicleEnrollmentCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public AddVehicleEnrollmentCommandAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(AddVehicleEnrollmentCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.AddVehicleEnrollment;
|
||||
|
||||
public class AddVehicleEnrollmentCommandHandler :
|
||||
IRequestHandler<AddVehicleEnrollmentCommand, VehicleEnrollmentDto>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
|
||||
public AddVehicleEnrollmentCommandHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper,
|
||||
IStringLocalizer localizer)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<VehicleEnrollmentDto> Handle(
|
||||
AddVehicleEnrollmentCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Check if the vehicle exists.
|
||||
|
||||
var vehicle = await _unitOfWork.VehicleRepository.GetOneAsync(
|
||||
e => e.Guid == request.VehicleGuid, cancellationToken);
|
||||
|
||||
if (vehicle == null)
|
||||
{
|
||||
throw new NotFoundException(
|
||||
$"Vehicle with Guid: {request.VehicleGuid} not found.");
|
||||
}
|
||||
|
||||
|
||||
// Check if the route exists.
|
||||
|
||||
var route = await _unitOfWork.RouteRepository.GetOneAsync(
|
||||
e => e.Guid == request.RouteGuid, e => e.RouteAddresses,
|
||||
cancellationToken);
|
||||
|
||||
if (route == null)
|
||||
{
|
||||
throw new NotFoundException(
|
||||
$"Route with Guid: {request.RouteGuid} not found.");
|
||||
}
|
||||
|
||||
|
||||
// Check if specified vehicle and route compatible.
|
||||
|
||||
if (vehicle.VehicleType != route.VehicleType)
|
||||
{
|
||||
throw new ValidationException(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new()
|
||||
{
|
||||
PropertyName = nameof(request.VehicleGuid),
|
||||
ErrorMessage = _localizer["Validation.SameVehicleType"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Check that request has the same Route Addresses
|
||||
// as a route data from datastore.
|
||||
|
||||
var sameRouteAddresses = route.RouteAddresses.All(
|
||||
ra => request.RouteAddressDetails.Any(
|
||||
rad => rad.RouteAddressGuid == ra.Guid));
|
||||
|
||||
if (!sameRouteAddresses)
|
||||
{
|
||||
throw new NotFoundException(
|
||||
$"Not all route addresses are found in a datastore.");
|
||||
}
|
||||
|
||||
// Check vehicle enrollments that might overlap with new one.
|
||||
|
||||
var requestDepartureTime = request.DepartureTime;
|
||||
|
||||
var requestTravelTime =
|
||||
request.RouteAddressDetails.Aggregate(
|
||||
TimeSpan.Zero, (sum, rad) => sum +
|
||||
rad.TimeToNextAddress + rad.CurrentAddressStopTime);
|
||||
|
||||
var requestArrivalTime = requestDepartureTime + requestTravelTime;
|
||||
|
||||
var enrollmentHistory =
|
||||
await _unitOfWork.VehicleEnrollmentRepository.GetPageAsync(
|
||||
e =>
|
||||
e.Vehicle.Guid == request.VehicleGuid &&
|
||||
e.DepartureTime >= DateTimeOffset.UtcNow.AddDays(-7),
|
||||
e => e.RouteAddressDetails,
|
||||
1, 200, cancellationToken);
|
||||
|
||||
// Three cases are included:
|
||||
//
|
||||
// ---RD---------SD----------RA--->
|
||||
// time
|
||||
//
|
||||
// ---RD---------SA----------RA--->
|
||||
// time
|
||||
//
|
||||
// ---SD-----RD-------RA-----SA--->
|
||||
// time
|
||||
// Where:
|
||||
// RD - request enrollment departure time
|
||||
// RA - request enrollment arrival time
|
||||
// SD - datastore enrollment (S for store) departure time
|
||||
// SA - datastore enrollment (S for store) arrival time
|
||||
|
||||
var overlappingWithOtherEnrollments = enrollmentHistory.Items
|
||||
.Where(ve =>
|
||||
{
|
||||
var departureTime = ve.DepartureTime;
|
||||
|
||||
var arrivalTime =
|
||||
ve.DepartureTime +
|
||||
ve.RouteAddressDetails
|
||||
.Aggregate(
|
||||
TimeSpan.Zero,
|
||||
(sum, rad) => sum +
|
||||
rad.TimeToNextAddress +
|
||||
rad.CurrentAddressStopTime);
|
||||
|
||||
return
|
||||
(departureTime >= requestDepartureTime &&
|
||||
departureTime <= requestArrivalTime) ||
|
||||
(arrivalTime >= requestDepartureTime &&
|
||||
arrivalTime <= requestArrivalTime) ||
|
||||
(departureTime <= requestDepartureTime &&
|
||||
arrivalTime >= requestArrivalTime);
|
||||
})
|
||||
.Any();
|
||||
|
||||
if (overlappingWithOtherEnrollments)
|
||||
{
|
||||
throw new ValidationException(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new()
|
||||
{
|
||||
PropertyName = nameof(request.DepartureTime),
|
||||
ErrorMessage = _localizer["Validation." +
|
||||
"VehicleEnrollments.OverlapWithOther"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Create entity and add to datastore.
|
||||
|
||||
var entity = new VehicleEnrollment()
|
||||
{
|
||||
DepartureTime = request.DepartureTime,
|
||||
Currency = request.Currency,
|
||||
VehicleId = vehicle.Id,
|
||||
RouteId = route.Id,
|
||||
RouteAddressDetails = route.RouteAddresses
|
||||
.OrderBy(ra => ra.Order)
|
||||
.Select(ra => new RouteAddressDetail()
|
||||
{
|
||||
TimeToNextAddress = request.RouteAddressDetails
|
||||
.First(rad => rad.RouteAddressGuid == ra.Guid)
|
||||
.TimeToNextAddress,
|
||||
CostToNextAddress = request.RouteAddressDetails
|
||||
.First(rad => rad.RouteAddressGuid == ra.Guid)
|
||||
.CostToNextAddress,
|
||||
CurrentAddressStopTime = request.RouteAddressDetails
|
||||
.First(rad => rad.RouteAddressGuid == ra.Guid)
|
||||
.CurrentAddressStopTime,
|
||||
RouteAddressId = ra.Id
|
||||
})
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
entity = await _unitOfWork.VehicleEnrollmentRepository.AddOneAsync(
|
||||
entity, cancellationToken);
|
||||
|
||||
await _unitOfWork.SaveAsync(cancellationToken);
|
||||
|
||||
// Hydrate vehicle enrollment with address information
|
||||
|
||||
var routeAddresses = await _unitOfWork.RouteAddressRepository
|
||||
.GetPageAsync(
|
||||
e =>
|
||||
request.RouteAddressDetails
|
||||
.Select(rad => rad.RouteAddressGuid)
|
||||
.Contains(e.Guid),
|
||||
e => e.Address.City.Region.Country,
|
||||
1, entity.RouteAddressDetails.Count(),
|
||||
cancellationToken);
|
||||
|
||||
foreach (var rad in entity.RouteAddressDetails)
|
||||
{
|
||||
rad.RouteAddress = routeAddresses.Items
|
||||
.First(ra => ra.Id == rad.RouteAddressId);
|
||||
}
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<VehicleEnrollmentDto>(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.AddVehicleEnrollment;
|
||||
|
||||
public class AddVehicleEnrollmentCommandValidator :
|
||||
AbstractValidator<AddVehicleEnrollmentCommand>
|
||||
{
|
||||
public AddVehicleEnrollmentCommandValidator(
|
||||
IStringLocalizer localizer,
|
||||
CultureService cultureService,
|
||||
TimeZoneService timeZoneService)
|
||||
{
|
||||
RuleFor(v => v.DepartureTime)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.Must(dt => dt >= DateTimeOffset.Now)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.DateTimeOffset.GreaterThanOrEqualTo"],
|
||||
DateTimeOffset.Now.ToOffset(timeZoneService.TimeZone.BaseUtcOffset)));
|
||||
|
||||
RuleFor(v => v.Currency)
|
||||
.Must(c => Currency.Enumerations.ContainsValue(c))
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
localizer["FluentValidation.MustBeInEnum"],
|
||||
String.Join(
|
||||
", ",
|
||||
Currency.Enumerations.Values.Select(e => e.Name))));
|
||||
|
||||
RuleFor(v => v.RouteAddressDetails.Count)
|
||||
.GreaterThanOrEqualTo(2)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||
2));
|
||||
|
||||
RuleFor(v => v.RouteAddressDetails)
|
||||
.Must(v => v.All(rad => rad.RouteAddressGuid != Guid.Empty))
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.Must(v => v.All(rad => rad.TimeToNextAddress >= TimeSpan.Zero))
|
||||
.WithMessage(localizer["VehicleEnrollments.NegativeTime"])
|
||||
.Must(v => v.All(rad => rad.CostToNextAddress >= 0))
|
||||
.WithMessage(localizer["VehicleEnrollments.NegativeCost"])
|
||||
.Must(v => v.All(rad => rad.CurrentAddressStopTime >= TimeSpan.Zero))
|
||||
.WithMessage(localizer["VehicleEnrollments.NegativeTime"]);
|
||||
|
||||
|
||||
RuleFor(v => v.VehicleGuid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
|
||||
RuleFor(v => v.RouteGuid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Commands.DeleteVehicleEnrollment;
|
||||
|
||||
public record DeleteVehicleEnrollmentCommand : IRequest
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Commands.DeleteVehicleEnrollment;
|
||||
|
||||
public class DeleteVehicleEnrollmentCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<DeleteVehicleEnrollmentCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public DeleteVehicleEnrollmentCommandAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(DeleteVehicleEnrollmentCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Commands.DeleteVehicleEnrollment;
|
||||
|
||||
public class DeleteVehicleEnrollmentCommandHandler :
|
||||
IRequestHandler<DeleteVehicleEnrollmentCommand>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
|
||||
public DeleteVehicleEnrollmentCommandHandler(UnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task Handle(
|
||||
DeleteVehicleEnrollmentCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _unitOfWork.VehicleEnrollmentRepository.GetOneAsync(
|
||||
e => e.Guid == request.Guid, cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// TODO: Check for tickets bought for this enrollment.
|
||||
// Decide whether to cancel tickets or do not allow deletion.
|
||||
|
||||
await _unitOfWork.VehicleEnrollmentRepository.DeleteOneAsync(
|
||||
entity, cancellationToken);
|
||||
|
||||
await _unitOfWork.SaveAsync(cancellationToken);
|
||||
_unitOfWork.Dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Commands.DeleteVehicleEnrollment;
|
||||
|
||||
public class DeleteVehicleEnrollmentCommandValidator : AbstractValidator<DeleteVehicleEnrollmentCommand>
|
||||
{
|
||||
public DeleteVehicleEnrollmentCommandValidator(IStringLocalizer localizer)
|
||||
{
|
||||
RuleFor(v => v.Guid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments.Models;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.UpdateVehicleEnrollment;
|
||||
|
||||
public record UpdateVehicleEnrollmentCommand : IRequest<VehicleEnrollmentDto>
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
|
||||
public DateTimeOffset DepartureTime { get; set; }
|
||||
|
||||
public Currency Currency { get; set; }
|
||||
|
||||
|
||||
public ICollection<RouteAddressDetailModel> RouteAddressDetails { get; set; }
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.UpdateVehicleEnrollment;
|
||||
|
||||
public class UpdateVehicleEnrollmentCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<UpdateVehicleEnrollmentCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public UpdateVehicleEnrollmentCommandAuthorizer(
|
||||
SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(UpdateVehicleEnrollmentCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.UpdateVehicleEnrollment;
|
||||
|
||||
public class UpdateVehicleEnrollmentCommandHandler :
|
||||
IRequestHandler<UpdateVehicleEnrollmentCommand, VehicleEnrollmentDto>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
|
||||
public UpdateVehicleEnrollmentCommandHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper,
|
||||
IStringLocalizer localizer)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<VehicleEnrollmentDto> Handle(
|
||||
UpdateVehicleEnrollmentCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Check for tickets bought for this enrollment.
|
||||
// Decide whether allow or not to perform update action.
|
||||
|
||||
var entity = await _unitOfWork.VehicleEnrollmentRepository.GetOneAsync(
|
||||
e => e.Guid == request.Guid, e => e.RouteAddressDetails,
|
||||
cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Check that request has the same Route Addresses
|
||||
// as a route data from datastore.
|
||||
|
||||
var route = await _unitOfWork.RouteRepository.GetOneAsync(
|
||||
e => e.Id == entity.RouteId, e => e.RouteAddresses,
|
||||
cancellationToken);
|
||||
|
||||
var sameRouteAddresses = route.RouteAddresses.All(
|
||||
ra => request.RouteAddressDetails.Any(
|
||||
rad => rad.RouteAddressGuid == ra.Guid));
|
||||
|
||||
if (!sameRouteAddresses)
|
||||
{
|
||||
throw new NotFoundException(
|
||||
$"Not all route addresses are found in a datastore.");
|
||||
}
|
||||
|
||||
|
||||
// Check vehicle enrollments that might overlap with updated one.
|
||||
// Exclude this vehicle enrollment.
|
||||
|
||||
var requestDepartureTime = request.DepartureTime;
|
||||
|
||||
var requestTravelTime =
|
||||
request.RouteAddressDetails.Aggregate(
|
||||
TimeSpan.Zero, (sum, rad) => sum +
|
||||
rad.TimeToNextAddress + rad.CurrentAddressStopTime);
|
||||
|
||||
var requestArrivalTime = requestDepartureTime + requestTravelTime;
|
||||
|
||||
var enrollmentHistory =
|
||||
await _unitOfWork.VehicleEnrollmentRepository.GetPageAsync(
|
||||
e =>
|
||||
e.Vehicle.Id == entity.VehicleId &&
|
||||
e.Id != entity.Id &&
|
||||
e.DepartureTime >= DateTimeOffset.UtcNow.AddDays(-7),
|
||||
e => e.RouteAddressDetails,
|
||||
1, 200, cancellationToken);
|
||||
|
||||
// Three cases are included:
|
||||
//
|
||||
// ---RD---------SD----------RA--->
|
||||
// time
|
||||
//
|
||||
// ---RD---------SA----------RA--->
|
||||
// time
|
||||
//
|
||||
// ---SD-----RD-------RA-----SA--->
|
||||
// time
|
||||
// Where:
|
||||
// RD - request enrollment departure time
|
||||
// RA - request enrollment arrival time
|
||||
// SD - datastore enrollment (S for store) departure time
|
||||
// SA - datastore enrollment (S for store) arrival time
|
||||
|
||||
var overlappingWithOtherEnrollments = enrollmentHistory.Items
|
||||
.Where(ve =>
|
||||
{
|
||||
var departureTime = ve.DepartureTime;
|
||||
|
||||
var arrivalTime =
|
||||
ve.DepartureTime +
|
||||
ve.RouteAddressDetails
|
||||
.Aggregate(
|
||||
TimeSpan.Zero,
|
||||
(sum, rad) => sum +
|
||||
rad.TimeToNextAddress +
|
||||
rad.CurrentAddressStopTime);
|
||||
|
||||
return
|
||||
(departureTime >= requestDepartureTime &&
|
||||
departureTime <= requestArrivalTime) ||
|
||||
(arrivalTime >= requestDepartureTime &&
|
||||
arrivalTime <= requestArrivalTime) ||
|
||||
(departureTime <= requestDepartureTime &&
|
||||
arrivalTime >= requestArrivalTime);
|
||||
})
|
||||
.Any();
|
||||
|
||||
if (overlappingWithOtherEnrollments)
|
||||
{
|
||||
throw new ValidationException(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new()
|
||||
{
|
||||
PropertyName = nameof(request.DepartureTime),
|
||||
ErrorMessage = _localizer["Validation." +
|
||||
"VehicleEnrollments.OverlapWithOther"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Update entity and add to datastore.
|
||||
|
||||
entity.DepartureTime = request.DepartureTime;
|
||||
entity.Currency = request.Currency;
|
||||
|
||||
foreach (var rad in entity.RouteAddressDetails)
|
||||
{
|
||||
var correspondingRouteAddress = route.RouteAddresses
|
||||
.First(ra => ra.Id == rad.RouteAddressId);
|
||||
|
||||
rad.TimeToNextAddress = request.RouteAddressDetails
|
||||
.First(rrad => rrad.RouteAddressGuid == rad.RouteAddress.Guid)
|
||||
.TimeToNextAddress;
|
||||
rad.CostToNextAddress = request.RouteAddressDetails
|
||||
.First(rrad => rrad.RouteAddressGuid == rad.RouteAddress.Guid)
|
||||
.CostToNextAddress;
|
||||
rad.CurrentAddressStopTime = request.RouteAddressDetails
|
||||
.First(rrad => rrad.RouteAddressGuid == rad.RouteAddress.Guid)
|
||||
.CurrentAddressStopTime;
|
||||
}
|
||||
|
||||
entity = await _unitOfWork.VehicleEnrollmentRepository.UpdateOneAsync(
|
||||
entity, cancellationToken);
|
||||
|
||||
await _unitOfWork.SaveAsync(cancellationToken);
|
||||
|
||||
// Hydrate vehicle enrollment with address information
|
||||
|
||||
var routeAddresses = await _unitOfWork.RouteAddressRepository
|
||||
.GetPageAsync(
|
||||
e =>
|
||||
entity.RouteAddressDetails
|
||||
.Select(rad => rad.RouteAddressId)
|
||||
.Contains(e.Id),
|
||||
e => e.Address.City.Region.Country,
|
||||
1, entity.RouteAddressDetails.Count(),
|
||||
cancellationToken);
|
||||
|
||||
foreach (var rad in entity.RouteAddressDetails)
|
||||
{
|
||||
rad.RouteAddress = routeAddresses.Items
|
||||
.First(ra => ra.Id == rad.RouteAddressId);
|
||||
}
|
||||
|
||||
entity.RouteAddressDetails = entity.RouteAddressDetails
|
||||
.OrderBy(rad => rad.RouteAddress.Order)
|
||||
.ToArray();
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<VehicleEnrollmentDto>(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.UpdateVehicleEnrollment;
|
||||
|
||||
public class UpdateVehicleEnrollmentCommandValidator :
|
||||
AbstractValidator<UpdateVehicleEnrollmentCommand>
|
||||
{
|
||||
public UpdateVehicleEnrollmentCommandValidator(
|
||||
IStringLocalizer localizer,
|
||||
CultureService cultureService,
|
||||
TimeZoneService timeZoneService)
|
||||
{
|
||||
RuleFor(v => v.Guid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
|
||||
RuleFor(v => v.DepartureTime)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.Must(dt => dt >= DateTimeOffset.Now)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.DateTimeOffset.GreaterThanOrEqualTo"],
|
||||
DateTimeOffset.Now.ToOffset(timeZoneService.TimeZone.BaseUtcOffset)));
|
||||
|
||||
RuleFor(v => v.Currency)
|
||||
.Must(c => Currency.Enumerations.ContainsValue(c))
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
localizer["FluentValidation.MustBeInEnum"],
|
||||
String.Join(
|
||||
", ",
|
||||
Currency.Enumerations.Values.Select(e => e.Name))));
|
||||
|
||||
RuleFor(v => v.RouteAddressDetails.Count)
|
||||
.GreaterThanOrEqualTo(2)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||
2));
|
||||
|
||||
RuleFor(v => v.RouteAddressDetails)
|
||||
.Must(v => v.All(rad => rad.RouteAddressGuid != Guid.Empty))
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.Must(v => v.All(rad => rad.TimeToNextAddress >= TimeSpan.Zero))
|
||||
.WithMessage(localizer["VehicleEnrollments.NegativeTime"])
|
||||
.Must(v => v.All(rad => rad.CostToNextAddress >= 0))
|
||||
.WithMessage(localizer["VehicleEnrollments.NegativeCost"])
|
||||
.Must(v => v.All(rad => rad.CurrentAddressStopTime >= TimeSpan.Zero))
|
||||
.WithMessage(localizer["VehicleEnrollments.NegativeTime"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Models;
|
||||
|
||||
public sealed class RouteAddressDetailModel
|
||||
{
|
||||
public TimeSpan TimeToNextAddress { get; set; }
|
||||
|
||||
public decimal CostToNextAddress { get; set; }
|
||||
|
||||
public TimeSpan CurrentAddressStopTime { get; set; }
|
||||
|
||||
|
||||
public Guid RouteAddressGuid { get; set; }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollment;
|
||||
|
||||
public record GetVehicleEnrollmentQuery : IRequest<VehicleEnrollmentDto>
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollment;
|
||||
|
||||
public class GetVehicleEnrollmentQueryAuthorizer :
|
||||
AbstractRequestAuthorizer<GetVehicleEnrollmentQuery>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public GetVehicleEnrollmentQueryAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(GetVehicleEnrollmentQuery request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using AutoMapper;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollment;
|
||||
|
||||
public class GetVehicleEnrollmentQueryHandler :
|
||||
IRequestHandler<GetVehicleEnrollmentQuery, VehicleEnrollmentDto>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetVehicleEnrollmentQueryHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<VehicleEnrollmentDto> Handle(
|
||||
GetVehicleEnrollmentQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _unitOfWork.VehicleEnrollmentRepository.GetOneAsync(
|
||||
e => e.Guid == request.Guid, e => e.RouteAddressDetails,
|
||||
cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Hydrate vehicle enrollment with address information
|
||||
|
||||
var routeAddresses = await _unitOfWork.RouteAddressRepository
|
||||
.GetPageAsync(
|
||||
e =>
|
||||
entity.RouteAddressDetails
|
||||
.Select(rad => rad.RouteAddressId)
|
||||
.Contains(e.Id),
|
||||
e => e.Address.City.Region.Country,
|
||||
1, entity.RouteAddressDetails.Count(),
|
||||
cancellationToken);
|
||||
|
||||
foreach (var rad in entity.RouteAddressDetails)
|
||||
{
|
||||
rad.RouteAddress = routeAddresses.Items
|
||||
.First(ra => ra.Id == rad.RouteAddressId);
|
||||
}
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<VehicleEnrollmentDto>(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Queries.GetVehicleEnrollment;
|
||||
|
||||
public class GetVehicleEnrollmentQueryValidator : AbstractValidator<GetVehicleEnrollmentQuery>
|
||||
{
|
||||
public GetVehicleEnrollmentQueryValidator(IStringLocalizer localizer)
|
||||
{
|
||||
RuleFor(v => v.Guid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollmentsPage;
|
||||
|
||||
public record GetVehicleEnrollmentsPageQuery :
|
||||
IRequest<PaginatedList<VehicleEnrollmentDto>>
|
||||
{
|
||||
public int PageNumber { get; set; } = 1;
|
||||
|
||||
public int PageSize { get; set; } = 10;
|
||||
|
||||
// public string Search { get; set; } = String.Empty;
|
||||
|
||||
public string Sort { get; set; } = String.Empty;
|
||||
|
||||
public Guid? RouteGuid { get; set; }
|
||||
|
||||
public Guid? VehicleGuid { get; set; }
|
||||
|
||||
public int? NumberOfAddressesGreaterThanOrEqual { get; set; }
|
||||
|
||||
public int? NumberOfAddressesLessThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? DepartureTimeGreaterThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? DepartureTimeLessThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? ArrivalTimeGreaterThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? ArrivalTimeLessThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TravelTimeGreaterThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TravelTimeLessThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeMovingGreaterThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeMovingLessThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeInStopsGreaterThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeInStopsLessThanOrEqual { get; set; }
|
||||
|
||||
public decimal? CostGreaterThanOrEqual { get; set; }
|
||||
|
||||
public decimal? CostLessThanOrEqual { get; set; }
|
||||
|
||||
public Currency? Currency { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Queries.GetVehicleEnrollmentsPage;
|
||||
|
||||
public class GetVehicleEnrollmentsPageQueryAuthorizer :
|
||||
AbstractRequestAuthorizer<GetVehicleEnrollmentsPageQuery>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public GetVehicleEnrollmentsPageQueryAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(GetVehicleEnrollmentsPageQuery request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollmentsPage;
|
||||
|
||||
public class GetVehicleEnrollmentsPageQueryHandler :
|
||||
IRequestHandler<GetVehicleEnrollmentsPageQuery, PaginatedList<VehicleEnrollmentDto>>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetVehicleEnrollmentsPageQueryHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<PaginatedList<VehicleEnrollmentDto>> Handle(
|
||||
GetVehicleEnrollmentsPageQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Add search functionality or remove it
|
||||
var paginatedList = await _unitOfWork.VehicleEnrollmentRepository.GetPageAsync(
|
||||
e =>
|
||||
// (e.Name.ToLower().Contains(request.Search.ToLower()) ||
|
||||
// e.City.Region.Country.Name.ToLower().Contains(request.Search.ToLower())) &&
|
||||
(request.RouteGuid != null
|
||||
? e.Route.Guid == request.RouteGuid
|
||||
: true) &&
|
||||
(request.VehicleGuid != null
|
||||
? e.Vehicle.Guid >= request.VehicleGuid
|
||||
: true) &&
|
||||
(request.NumberOfAddressesGreaterThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails.Count() >=
|
||||
request.NumberOfAddressesGreaterThanOrEqual
|
||||
: true) &&
|
||||
(request.NumberOfAddressesLessThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails.Count() <=
|
||||
request.NumberOfAddressesLessThanOrEqual
|
||||
: true) &&
|
||||
(request.DepartureTimeGreaterThanOrEqual != null
|
||||
? e.DepartureTime >= request.DepartureTimeGreaterThanOrEqual
|
||||
: true) &&
|
||||
(request.DepartureTimeLessThanOrEqual != null
|
||||
? e.DepartureTime <= request.DepartureTimeLessThanOrEqual
|
||||
: true) &&
|
||||
(request.ArrivalTimeGreaterThanOrEqual != null
|
||||
?
|
||||
e.DepartureTime.AddSeconds(e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds + rad.CurrentAddressStopTime.TotalSeconds)) >=
|
||||
request.ArrivalTimeGreaterThanOrEqual
|
||||
: true) &&
|
||||
(request.ArrivalTimeLessThanOrEqual != null
|
||||
?
|
||||
e.DepartureTime.AddSeconds(e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds + rad.CurrentAddressStopTime.TotalSeconds)) <=
|
||||
request.ArrivalTimeLessThanOrEqual
|
||||
: true) &&
|
||||
(request.TravelTimeGreaterThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds) >=
|
||||
request.TravelTimeGreaterThanOrEqual.Value.TotalSeconds
|
||||
: true) &&
|
||||
(request.TravelTimeLessThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds) <=
|
||||
request.TravelTimeLessThanOrEqual.Value.TotalSeconds
|
||||
: true) &&
|
||||
(request.TimeMovingGreaterThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds) >=
|
||||
request.TimeMovingGreaterThanOrEqual.Value.TotalSeconds
|
||||
: true) &&
|
||||
(request.TimeMovingLessThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails
|
||||
.Sum(rad => rad.TimeToNextAddress.TotalSeconds) <=
|
||||
request.TimeMovingLessThanOrEqual.Value.TotalSeconds
|
||||
: true) &&
|
||||
(request.TimeInStopsGreaterThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails
|
||||
.Sum(rad => rad.CurrentAddressStopTime.TotalSeconds) >=
|
||||
request.TimeInStopsGreaterThanOrEqual.Value.TotalSeconds
|
||||
: true) &&
|
||||
(request.TimeInStopsLessThanOrEqual != null
|
||||
?
|
||||
e.RouteAddressDetails
|
||||
.Sum(rad => rad.CurrentAddressStopTime.TotalSeconds) <=
|
||||
request.TimeInStopsLessThanOrEqual.Value.TotalSeconds
|
||||
: true) &&
|
||||
(request.CostGreaterThanOrEqual != null
|
||||
? e.RouteAddressDetails.Sum(rad => rad.CostToNextAddress) >=
|
||||
request.CostGreaterThanOrEqual
|
||||
: true) &&
|
||||
(request.CostLessThanOrEqual != null
|
||||
? e.RouteAddressDetails.Sum(rad => rad.CostToNextAddress) <=
|
||||
request.CostLessThanOrEqual
|
||||
: true) &&
|
||||
(request.Currency != null
|
||||
? e.Currency == request.Currency
|
||||
: true),
|
||||
e => e.RouteAddressDetails,
|
||||
request.PageNumber, request.PageSize,
|
||||
cancellationToken);
|
||||
|
||||
|
||||
// Hydrate vehicle enrollment with address information
|
||||
|
||||
var routeAddressIds = paginatedList.Items
|
||||
.SelectMany(ve => ve.RouteAddressDetails)
|
||||
.Select(rad => rad.RouteAddressId);
|
||||
|
||||
var routeAddresses = await _unitOfWork.RouteAddressRepository
|
||||
.GetPageAsync(
|
||||
e =>
|
||||
routeAddressIds.Contains(e.Id),
|
||||
e => e.Address.City.Region.Country,
|
||||
1, paginatedList.Items.Sum(e => e.RouteAddressDetails.Count()),
|
||||
cancellationToken);
|
||||
|
||||
foreach (var vehicleEnrollment in paginatedList.Items)
|
||||
{
|
||||
foreach (var routeAddressDetail in
|
||||
vehicleEnrollment.RouteAddressDetails)
|
||||
{
|
||||
routeAddressDetail.RouteAddress = routeAddresses.Items
|
||||
.First(ra => ra.Id == routeAddressDetail.RouteAddressId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var mappedItems = _mapper
|
||||
.ProjectTo<VehicleEnrollmentDto>(paginatedList.Items.AsQueryable());
|
||||
|
||||
mappedItems = QueryableExtension<VehicleEnrollmentDto>
|
||||
.ApplySort(mappedItems, request.Sort);
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return new PaginatedList<VehicleEnrollmentDto>(
|
||||
mappedItems.ToList(),
|
||||
paginatedList.TotalCount, request.PageNumber,
|
||||
request.PageSize);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.Queries.GetVehicleEnrollmentsPage;
|
||||
|
||||
public class GetVehicleEnrollmentsPageQueryValidator : AbstractValidator<GetVehicleEnrollmentsPageQuery>
|
||||
{
|
||||
public GetVehicleEnrollmentsPageQueryValidator(
|
||||
IStringLocalizer localizer,
|
||||
CultureService cultureService)
|
||||
{
|
||||
RuleFor(v => v.PageNumber)
|
||||
.GreaterThanOrEqualTo(1)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||
1));
|
||||
|
||||
RuleFor(v => v.PageSize)
|
||||
.GreaterThanOrEqualTo(1)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||
1))
|
||||
.LessThanOrEqualTo(50)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.LessThanOrEqualTo"],
|
||||
50));
|
||||
|
||||
// RuleFor(v => v.Search)
|
||||
// .MaximumLength(64)
|
||||
// .WithMessage(
|
||||
// String.Format(
|
||||
// cultureService.Culture,
|
||||
// localizer["FluentValidation.MaximumLength"],
|
||||
// 64));
|
||||
}
|
||||
}
|
29
src/Application/VehicleEnrollments/RouteAddressDetailDto.cs
Normal file
29
src/Application/VehicleEnrollments/RouteAddressDetailDto.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments;
|
||||
|
||||
public sealed class RouteAddressDetailDto : IMapFrom<RouteAddressDetail>
|
||||
{
|
||||
public TimeSpan TimeToNextAddress { get; set; }
|
||||
|
||||
public decimal CostToNextAddress { get; set; }
|
||||
|
||||
public TimeSpan CurrentAddressStopTime { get; set; }
|
||||
|
||||
public Guid RouteAddressUuid { get; set; }
|
||||
|
||||
|
||||
public VehicleEnrollmentRouteAddressDto RouteAddress { get; set; }
|
||||
|
||||
public void Mapping(MappingProfile profile)
|
||||
{
|
||||
profile.CreateMap<RouteAddressDetail, RouteAddressDetailDto>()
|
||||
.ForMember(
|
||||
d => d.RouteAddressUuid,
|
||||
opt => opt.MapFrom(s => s.RouteAddress.Guid))
|
||||
.ForMember(
|
||||
d => d.RouteAddress,
|
||||
opt => opt.MapFrom(s => s.RouteAddress));
|
||||
}
|
||||
}
|
50
src/Application/VehicleEnrollments/VehicleEnrollmentDto.cs
Normal file
50
src/Application/VehicleEnrollments/VehicleEnrollmentDto.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||
using cuqmbr.TravelGuide.Application.Common.Mappings.Resolvers;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments;
|
||||
|
||||
public sealed class VehicleEnrollmentDto : IMapFrom<VehicleEnrollment>
|
||||
{
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
public DateTimeOffset DepartureTime { get; set; }
|
||||
|
||||
public DateTimeOffset ArrivalTime =>
|
||||
DepartureTime +
|
||||
RouteAddressDetails.Aggregate(TimeSpan.Zero, (sum, next) =>
|
||||
sum + next.TimeToNextAddress + next.CurrentAddressStopTime);
|
||||
|
||||
public TimeSpan TravelTime =>
|
||||
RouteAddressDetails.Aggregate(TimeSpan.Zero, (sum, next) =>
|
||||
sum + next.TimeToNextAddress + next.CurrentAddressStopTime);
|
||||
|
||||
public TimeSpan TimeMoving =>
|
||||
RouteAddressDetails.Aggregate(TimeSpan.Zero, (sum, next) =>
|
||||
sum + next.TimeToNextAddress);
|
||||
|
||||
public TimeSpan TimeInStops =>
|
||||
RouteAddressDetails.Aggregate(TimeSpan.Zero, (sum, next) =>
|
||||
sum + next.CurrentAddressStopTime);
|
||||
|
||||
public decimal TotalCost =>
|
||||
RouteAddressDetails.Aggregate((decimal)0, (sum, next) =>
|
||||
sum + next.CostToNextAddress);
|
||||
|
||||
public string Currency { get; set; }
|
||||
|
||||
public ICollection<RouteAddressDetailDto> RouteAddressDetails { get; set; }
|
||||
|
||||
public void Mapping(MappingProfile profile)
|
||||
{
|
||||
profile.CreateMap<VehicleEnrollment, VehicleEnrollmentDto>()
|
||||
.ForMember(
|
||||
d => d.Uuid,
|
||||
opt => opt.MapFrom(s => s.Guid))
|
||||
.ForMember(
|
||||
d => d.DepartureTime,
|
||||
opt => opt
|
||||
.MapFrom<DateTimeOffsetToLocalResolver, DateTimeOffset>(
|
||||
s => s.DepartureTime));
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments;
|
||||
|
||||
public sealed class VehicleEnrollmentRouteAddressDto : IMapFrom<RouteAddress>
|
||||
{
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
public short Order { get; set; }
|
||||
|
||||
|
||||
public Guid AddressUuid { get; set; }
|
||||
|
||||
public string AddressName { get; set; }
|
||||
|
||||
public double AddressLongitude { get; set; }
|
||||
|
||||
public double AddressLatitude { get; set; }
|
||||
|
||||
public string AddressVehicleType { get; set; }
|
||||
|
||||
public Guid CountryUuid { get; set; }
|
||||
|
||||
public string CountryName { get; set; }
|
||||
|
||||
public Guid RegionUuid { get; set; }
|
||||
|
||||
public string RegionName { get; set; }
|
||||
|
||||
public Guid CityUuid { get; set; }
|
||||
|
||||
public string CityName { get; set; }
|
||||
|
||||
public void Mapping(MappingProfile profile)
|
||||
{
|
||||
profile.CreateMap<RouteAddress, VehicleEnrollmentRouteAddressDto>()
|
||||
.ForMember(
|
||||
d => d.Uuid,
|
||||
opt => opt.MapFrom(s => s.Guid))
|
||||
.ForMember(
|
||||
d => d.AddressUuid,
|
||||
opt => opt.MapFrom(s => s.Address.Guid))
|
||||
.ForMember(
|
||||
d => d.AddressName,
|
||||
opt => opt.MapFrom(s => s.Address.Name))
|
||||
.ForMember(
|
||||
d => d.AddressLongitude,
|
||||
opt => opt.MapFrom(s => s.Address.Longitude))
|
||||
.ForMember(
|
||||
d => d.AddressLatitude,
|
||||
opt => opt.MapFrom(s => s.Address.Latitude))
|
||||
.ForMember(
|
||||
d => d.AddressVehicleType,
|
||||
opt => opt.MapFrom(s => s.Address.VehicleType.Name))
|
||||
.ForMember(
|
||||
d => d.CityUuid,
|
||||
opt => opt.MapFrom(s => s.Address.City.Guid))
|
||||
.ForMember(
|
||||
d => d.CityName,
|
||||
opt => opt.MapFrom(s => s.Address.City.Name))
|
||||
.ForMember(
|
||||
d => d.RegionUuid,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Guid))
|
||||
.ForMember(
|
||||
d => d.RegionName,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Name))
|
||||
.ForMember(
|
||||
d => d.CountryUuid,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Country.Guid))
|
||||
.ForMember(
|
||||
d => d.CountryName,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Country.Name));
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.ViewModels;
|
||||
|
||||
public sealed class AddVehicleEnrollmentViewModel
|
||||
{
|
||||
public DateTimeOffset DepartureTime { get; set; }
|
||||
|
||||
public string Currency { get; set; }
|
||||
|
||||
|
||||
public Guid VehicleUuid { get; set; }
|
||||
|
||||
public Guid RouteUuid { get; set; }
|
||||
|
||||
public ICollection<RouteAddressDetailViewModel> RouteAddressDetails { get; set; }
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.ViewModels;
|
||||
|
||||
public sealed class GetVehicleEnrollmentsPageFilterViewModel
|
||||
{
|
||||
public int PageNumber { get; set; } = 1;
|
||||
|
||||
public int PageSize { get; set; } = 10;
|
||||
|
||||
public string Search { get; set; } = String.Empty;
|
||||
|
||||
public string Sort { get; set; } = String.Empty;
|
||||
|
||||
public Guid? RouteGuid { get; set; }
|
||||
|
||||
public Guid? VehicleGuid { get; set; }
|
||||
|
||||
public int? NumberOfAddressesGreaterThanOrEqual { get; set; }
|
||||
|
||||
public int? NumberOfAddressesLessThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? DepartureTimeGreaterThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? DepartureTimeLessThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? ArrivalTimeGreaterThanOrEqual { get; set; }
|
||||
|
||||
public DateTimeOffset? ArrivalTimeLessThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TravelTimeGreaterThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TravelTimeLessThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeMovingGreaterThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeMovingLessThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeInStopsGreaterThanOrEqual { get; set; }
|
||||
|
||||
public TimeSpan? TimeInStopsLessThanOrEqual { get; set; }
|
||||
|
||||
public decimal? CostGreaterThanOrEqual { get; set; }
|
||||
|
||||
public decimal? CostLessThanOrEqual { get; set; }
|
||||
|
||||
public string? Currency { get; set; }
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.ViewModels;
|
||||
|
||||
public sealed class RouteAddressDetailViewModel
|
||||
{
|
||||
public TimeSpan TimeToNextAddress { get; set; }
|
||||
|
||||
public decimal CostToNextAddress { get; set; }
|
||||
|
||||
public TimeSpan CurrentAddressStopTime { get; set; }
|
||||
|
||||
|
||||
public Guid RouteAddressUuid { get; set; }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments.ViewModels;
|
||||
|
||||
public sealed class UpdateVehicleEnrollmentViewModel
|
||||
{
|
||||
public DateTimeOffset DepartureTime { get; set; }
|
||||
|
||||
public string Currency { get; set; }
|
||||
|
||||
|
||||
public ICollection<RouteAddressDetailViewModel> RouteAddressDetails { get; set; }
|
||||
}
|
@ -10,4 +10,6 @@ public sealed class Route : EntityBase
|
||||
|
||||
|
||||
public ICollection<RouteAddress> RouteAddresses { get; set; }
|
||||
|
||||
public ICollection<VehicleEnrollment> VehicleEnrollments { get; set; }
|
||||
}
|
||||
|
@ -13,4 +13,7 @@ public sealed class RouteAddress : EntityBase
|
||||
public long RouteId { get; set; }
|
||||
|
||||
public Route Route { get; set; }
|
||||
|
||||
|
||||
public ICollection<RouteAddressDetail> Details { get; set; }
|
||||
}
|
||||
|
20
src/Domain/Entities/RouteAddressDetail.cs
Normal file
20
src/Domain/Entities/RouteAddressDetail.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
public sealed class RouteAddressDetail : EntityBase
|
||||
{
|
||||
public TimeSpan TimeToNextAddress { get; set; }
|
||||
|
||||
public decimal CostToNextAddress { get; set; }
|
||||
|
||||
public TimeSpan CurrentAddressStopTime { get; set; }
|
||||
|
||||
|
||||
public long VehicleEnrollmentId { get; set; }
|
||||
|
||||
public VehicleEnrollment VehicleEnrollment { get; set; }
|
||||
|
||||
|
||||
public long RouteAddressId { get; set; }
|
||||
|
||||
public RouteAddress RouteAddress { get; set; }
|
||||
}
|
@ -5,4 +5,7 @@ namespace cuqmbr.TravelGuide.Domain.Entities;
|
||||
public abstract class Vehicle : EntityBase
|
||||
{
|
||||
public VehicleType VehicleType { get; set; }
|
||||
|
||||
|
||||
public ICollection<VehicleEnrollment> Enrollments { get; set; }
|
||||
}
|
||||
|
23
src/Domain/Entities/VehicleEnrollment.cs
Normal file
23
src/Domain/Entities/VehicleEnrollment.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
public class VehicleEnrollment : EntityBase
|
||||
{
|
||||
public DateTimeOffset DepartureTime { get; set; }
|
||||
|
||||
public Currency Currency { get; set; }
|
||||
|
||||
|
||||
public long VehicleId { get; set; }
|
||||
|
||||
public Vehicle Vehicle { get; set; }
|
||||
|
||||
|
||||
public long RouteId { get; set; }
|
||||
|
||||
public Route Route { get; set; }
|
||||
|
||||
|
||||
public ICollection<RouteAddressDetail> RouteAddressDetails { get; set; }
|
||||
}
|
30
src/Domain/Enums/Currency.cs
Normal file
30
src/Domain/Enums/Currency.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace cuqmbr.TravelGuide.Domain.Enums;
|
||||
|
||||
// Do not forget to update the schema of your database when changing
|
||||
// this class (if you use it with a database)
|
||||
|
||||
// ISO-4217 Country Codes dated 2025-03-31
|
||||
|
||||
public abstract class Currency : Enumeration<Currency>
|
||||
{
|
||||
public static readonly Currency USD = new USDCurrency();
|
||||
public static readonly Currency EUR = new EURCurrency();
|
||||
public static readonly Currency UAH = new UAHCurrency();
|
||||
|
||||
protected Currency(int value, string name) : base(value, name) { }
|
||||
|
||||
private sealed class USDCurrency : Currency
|
||||
{
|
||||
public USDCurrency() : base(840, "USD") { }
|
||||
}
|
||||
|
||||
private sealed class EURCurrency : Currency
|
||||
{
|
||||
public EURCurrency() : base(978, "EUR") { }
|
||||
}
|
||||
|
||||
private sealed class UAHCurrency : Currency
|
||||
{
|
||||
public UAHCurrency() : base(980, "UAH") { }
|
||||
}
|
||||
}
|
@ -189,7 +189,8 @@ public class AddressesController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(AddressDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -177,7 +177,8 @@ public class AircraftsController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(AircraftDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -177,7 +177,8 @@ public class BusesController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(BusDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -172,7 +172,8 @@ public class CitiesController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(CityDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -160,7 +160,8 @@ public class CountriesController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(CountryDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -9,7 +9,6 @@ using cuqmbr.TravelGuide.Application.Regions.Queries.GetRegionsPage;
|
||||
using cuqmbr.TravelGuide.Application.Regions.Queries.GetRegion;
|
||||
using cuqmbr.TravelGuide.Application.Regions.Commands.UpdateRegion;
|
||||
using cuqmbr.TravelGuide.Application.Regions.Commands.DeleteRegion;
|
||||
using cuqmbr.TravelGuide.Application.Regions.ViewModels;
|
||||
|
||||
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||
|
||||
@ -49,7 +48,8 @@ public class RegionsController : ControllerBase
|
||||
await Mediator.Send(
|
||||
new AddRegionCommand()
|
||||
{
|
||||
Name = viewModel.Name
|
||||
Name = viewModel.Name,
|
||||
CountryGuid = viewModel.CountryUuid
|
||||
},
|
||||
cancellationToken));
|
||||
}
|
||||
@ -171,7 +171,8 @@ public class RegionsController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(RegionDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -182,7 +182,8 @@ public class RoutesController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(RouteDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
@ -177,7 +177,8 @@ public class TrainsController : ControllerBase
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(TrainDto))]
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
|
260
src/HttpApi/Controllers/VehicleEnrollmentsController.cs
Normal file
260
src/HttpApi/Controllers/VehicleEnrollmentsController.cs
Normal file
@ -0,0 +1,260 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Application.Common.ViewModels;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.AddVehicleEnrollment;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollmentsPage;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Queries.GetVehicleEnrollment;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.UpdateVehicleEnrollment;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments
|
||||
.Commands.DeleteVehicleEnrollment;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments.Models;
|
||||
using cuqmbr.TravelGuide.Application.VehicleEnrollments.ViewModels;
|
||||
|
||||
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||
|
||||
[Route("vehicleEnrollments")]
|
||||
public class VehicleEnrollmentsController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
[SwaggerOperation("Add a vehicle enrollment")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status201Created, "Object successfuly created",
|
||||
typeof(VehicleEnrollmentDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest,
|
||||
"Enrollment travel time overlapping with " +
|
||||
"other enrollment time of the vehicle",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Given route not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Given vehicle not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "At least one route address not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<ActionResult<VehicleEnrollmentDto>> Add(
|
||||
[FromBody] AddVehicleEnrollmentViewModel viewModel,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return StatusCode(
|
||||
StatusCodes.Status201Created,
|
||||
await Mediator.Send(
|
||||
new AddVehicleEnrollmentCommand()
|
||||
{
|
||||
DepartureTime = viewModel.DepartureTime,
|
||||
Currency = Currency.FromName(viewModel.Currency),
|
||||
VehicleGuid = viewModel.VehicleUuid,
|
||||
RouteGuid = viewModel.RouteUuid,
|
||||
RouteAddressDetails = viewModel.RouteAddressDetails.Select(
|
||||
rad => new RouteAddressDetailModel()
|
||||
{
|
||||
TimeToNextAddress = rad.TimeToNextAddress,
|
||||
CostToNextAddress = rad.CostToNextAddress,
|
||||
CurrentAddressStopTime = rad.CurrentAddressStopTime,
|
||||
RouteAddressGuid = rad.RouteAddressUuid
|
||||
})
|
||||
.ToArray()
|
||||
},
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[SwaggerOperation("Get a list of all vehicle enrollments")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status200OK, "Request successful",
|
||||
typeof(PaginatedList<VehicleEnrollmentDto>))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<PaginatedList<VehicleEnrollmentDto>> GetPage(
|
||||
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
|
||||
[FromQuery] SortQuery sortQuery,
|
||||
[FromQuery] GetVehicleEnrollmentsPageFilterViewModel filterQuery,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Mediator.Send(
|
||||
new GetVehicleEnrollmentsPageQuery()
|
||||
{
|
||||
PageNumber = pageQuery.PageNumber,
|
||||
PageSize = pageQuery.PageSize,
|
||||
// Search = searchQuery.Search,
|
||||
Sort = sortQuery.Sort,
|
||||
RouteGuid = filterQuery.RouteGuid,
|
||||
VehicleGuid = filterQuery.VehicleGuid,
|
||||
NumberOfAddressesGreaterThanOrEqual =
|
||||
filterQuery.NumberOfAddressesGreaterThanOrEqual,
|
||||
NumberOfAddressesLessThanOrEqual =
|
||||
filterQuery.NumberOfAddressesLessThanOrEqual,
|
||||
DepartureTimeGreaterThanOrEqual =
|
||||
filterQuery.DepartureTimeGreaterThanOrEqual,
|
||||
DepartureTimeLessThanOrEqual =
|
||||
filterQuery.DepartureTimeLessThanOrEqual,
|
||||
ArrivalTimeGreaterThanOrEqual =
|
||||
filterQuery.ArrivalTimeGreaterThanOrEqual,
|
||||
ArrivalTimeLessThanOrEqual =
|
||||
filterQuery.ArrivalTimeLessThanOrEqual,
|
||||
TravelTimeGreaterThanOrEqual =
|
||||
filterQuery.TravelTimeGreaterThanOrEqual,
|
||||
TravelTimeLessThanOrEqual =
|
||||
filterQuery.TravelTimeLessThanOrEqual,
|
||||
TimeMovingGreaterThanOrEqual =
|
||||
filterQuery.TimeMovingGreaterThanOrEqual,
|
||||
TimeMovingLessThanOrEqual =
|
||||
filterQuery.TimeMovingLessThanOrEqual,
|
||||
TimeInStopsGreaterThanOrEqual =
|
||||
filterQuery.TimeInStopsGreaterThanOrEqual,
|
||||
TimeInStopsLessThanOrEqual =
|
||||
filterQuery.TimeInStopsLessThanOrEqual,
|
||||
CostGreaterThanOrEqual =
|
||||
filterQuery.CostGreaterThanOrEqual,
|
||||
CostLessThanOrEqual =
|
||||
filterQuery.CostLessThanOrEqual,
|
||||
Currency = Currency.FromName(filterQuery.Currency)
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{uuid:guid}")]
|
||||
[SwaggerOperation("Get a vehicle enrollment by uuid")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status200OK, "Request successful",
|
||||
typeof(VehicleEnrollmentDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(VehicleEnrollmentDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<VehicleEnrollmentDto> Get(
|
||||
[FromRoute] Guid uuid,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Mediator.Send(
|
||||
new GetVehicleEnrollmentQuery() { Guid = uuid },
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{uuid:guid}")]
|
||||
[SwaggerOperation("Update a vehicle enrollment")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status200OK, "Request successful",
|
||||
typeof(VehicleEnrollmentDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest,
|
||||
"Enrollment travel time overlapping with " +
|
||||
"other enrollment time of the vehicle",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "At least one route address not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<VehicleEnrollmentDto> Update(
|
||||
[FromRoute] Guid uuid,
|
||||
[FromBody] UpdateVehicleEnrollmentViewModel viewModel,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Mediator.Send(
|
||||
new UpdateVehicleEnrollmentCommand()
|
||||
{
|
||||
Guid = uuid,
|
||||
DepartureTime = viewModel.DepartureTime,
|
||||
Currency = Currency.FromName(viewModel.Currency),
|
||||
RouteAddressDetails = viewModel.RouteAddressDetails.Select(
|
||||
rad => new RouteAddressDetailModel()
|
||||
{
|
||||
TimeToNextAddress = rad.TimeToNextAddress,
|
||||
CostToNextAddress = rad.CostToNextAddress,
|
||||
CurrentAddressStopTime = rad.CurrentAddressStopTime,
|
||||
RouteAddressGuid = rad.RouteAddressUuid
|
||||
})
|
||||
.ToArray()
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{uuid:guid}")]
|
||||
[SwaggerOperation("Delete a vehicle enrollment")]
|
||||
[SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<IActionResult> Delete(
|
||||
[FromRoute] Guid uuid,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await Mediator.Send(
|
||||
new DeleteVehicleEnrollmentCommand() { Guid = uuid },
|
||||
cancellationToken);
|
||||
return StatusCode(StatusCodes.Status204NoContent);
|
||||
}
|
||||
}
|
@ -27,5 +27,10 @@ public class InMemoryDbContext : DbContext
|
||||
.Properties<VehicleType>()
|
||||
.HaveColumnType("vehicle_type")
|
||||
.HaveConversion<VehicleTypeConverter>();
|
||||
|
||||
builder
|
||||
.Properties<Currency>()
|
||||
.HaveColumnType("currency")
|
||||
.HaveConversion<CurrencyConverter>();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
|
||||
BusRepository = new InMemoryBusRepository(_dbContext);
|
||||
AircraftRepository = new InMemoryAircraftRepository(_dbContext);
|
||||
TrainRepository = new InMemoryTrainRepository(_dbContext);
|
||||
VehicleEnrollmentRepository =
|
||||
new InMemoryVehicleEnrollmentRepository(_dbContext);
|
||||
RouteAddressRepository =
|
||||
new InMemoryRouteAddressRepository(_dbContext);
|
||||
}
|
||||
|
||||
public CountryRepository CountryRepository { get; init; }
|
||||
@ -42,6 +46,10 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
|
||||
|
||||
public TrainRepository TrainRepository { get; init; }
|
||||
|
||||
public VehicleEnrollmentRepository VehicleEnrollmentRepository { get; init; }
|
||||
|
||||
public RouteAddressRepository RouteAddressRepository { get; init; }
|
||||
|
||||
public int Save()
|
||||
{
|
||||
return _dbContext.SaveChanges();
|
||||
|
@ -0,0 +1,11 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.InMemory.Repositories;
|
||||
|
||||
public sealed class InMemoryRouteAddressRepository :
|
||||
InMemoryBaseRepository<RouteAddress>, RouteAddressRepository
|
||||
{
|
||||
public InMemoryRouteAddressRepository(InMemoryDbContext dbContext)
|
||||
: base(dbContext) { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.InMemory.Repositories;
|
||||
|
||||
public sealed class InMemoryVehicleEnrollmentRepository :
|
||||
InMemoryBaseRepository<VehicleEnrollment>, VehicleEnrollmentRepository
|
||||
{
|
||||
public InMemoryVehicleEnrollmentRepository(InMemoryDbContext dbContext)
|
||||
: base(dbContext) { }
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
|
||||
|
||||
public class RouteAddressDetailConfiguration :
|
||||
BaseConfiguration<RouteAddressDetail>
|
||||
{
|
||||
public override void Configure(EntityTypeBuilder<RouteAddressDetail> builder)
|
||||
{
|
||||
builder
|
||||
.ToTable("route_address_details");
|
||||
|
||||
base.Configure(builder);
|
||||
|
||||
|
||||
builder
|
||||
.Property(rad => rad.TimeToNextAddress)
|
||||
.HasColumnName("time_to_next_address")
|
||||
.HasColumnType("interval")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.Property(rad => rad.CostToNextAddress)
|
||||
.HasColumnName("cost_to_next_address")
|
||||
.HasColumnType("numeric(24,12)")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.Property(rad => rad.CurrentAddressStopTime)
|
||||
.HasColumnName("current_address_stop_time")
|
||||
.HasColumnType("interval")
|
||||
.IsRequired(true);
|
||||
|
||||
|
||||
builder
|
||||
.Property(rad => rad.VehicleEnrollmentId)
|
||||
.HasColumnName("vehicle_enrollment_id")
|
||||
.HasColumnType("bigint")
|
||||
.IsRequired(true);
|
||||
|
||||
|
||||
builder
|
||||
.HasOne(rad => rad.VehicleEnrollment)
|
||||
.WithMany(ve => ve.RouteAddressDetails)
|
||||
.HasForeignKey(rad => rad.VehicleEnrollmentId)
|
||||
.HasConstraintName(
|
||||
"fk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(rad => rad.VehicleEnrollmentId).Metadata.GetColumnName()}")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.HasIndex(rad => rad.VehicleEnrollmentId)
|
||||
.HasDatabaseName(
|
||||
"ix_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(rad => rad.VehicleEnrollmentId).Metadata.GetColumnName()}");
|
||||
|
||||
|
||||
builder
|
||||
.Property(rad => rad.RouteAddressId)
|
||||
.HasColumnName("route_address_id")
|
||||
.HasColumnType("bigint")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.HasOne(rad => rad.RouteAddress)
|
||||
.WithMany(ra => ra.Details)
|
||||
.HasForeignKey(rad => rad.RouteAddressId)
|
||||
.HasConstraintName(
|
||||
"fk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(rad => rad.RouteAddressId).Metadata.GetColumnName()}")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.HasIndex(rad => rad.RouteAddressId)
|
||||
.HasDatabaseName(
|
||||
"ix_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(rad => rad.RouteAddressId).Metadata.GetColumnName()}");
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
|
||||
|
||||
public class VehicleEnrollmentConfiguration : BaseConfiguration<VehicleEnrollment>
|
||||
{
|
||||
public override void Configure(EntityTypeBuilder<VehicleEnrollment> builder)
|
||||
{
|
||||
builder
|
||||
.Property(ve => ve.Currency)
|
||||
.HasColumnName("currency")
|
||||
.HasColumnType("varchar(8)")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.ToTable(
|
||||
"vehicle_enrollments",
|
||||
ve => ve.HasCheckConstraint(
|
||||
"ck_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ve => ve.Currency)
|
||||
.Metadata.GetColumnName()}",
|
||||
$"{builder.Property(ve => ve.Currency)
|
||||
.Metadata.GetColumnName()} IN ('{String
|
||||
.Join("', '", Currency.Enumerations
|
||||
.Values.Select(v => v.Name))}')"));
|
||||
|
||||
base.Configure(builder);
|
||||
|
||||
|
||||
builder
|
||||
.Property(ve => ve.DepartureTime)
|
||||
.HasColumnName("departure_time")
|
||||
.HasColumnType("timestamptz")
|
||||
.IsRequired(true);
|
||||
|
||||
|
||||
builder
|
||||
.Property(ve => ve.VehicleId)
|
||||
.HasColumnName("vehicle_id")
|
||||
.HasColumnType("bigint")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.HasOne(ve => ve.Vehicle)
|
||||
.WithMany(v => v.Enrollments)
|
||||
.HasForeignKey(ve => ve.VehicleId)
|
||||
.HasConstraintName(
|
||||
"fk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ve => ve.VehicleId).Metadata.GetColumnName()}")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.HasIndex(ve => ve.VehicleId)
|
||||
.HasDatabaseName(
|
||||
"ix_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ve => ve.VehicleId).Metadata.GetColumnName()}");
|
||||
|
||||
|
||||
builder
|
||||
.Property(ve => ve.RouteId)
|
||||
.HasColumnName("route_id")
|
||||
.HasColumnType("bigint")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.HasOne(ve => ve.Route)
|
||||
.WithMany(r => r.VehicleEnrollments)
|
||||
.HasForeignKey(ve => ve.RouteId)
|
||||
.HasConstraintName(
|
||||
"fk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ve => ve.RouteId).Metadata.GetColumnName()}")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.HasIndex(ve => ve.RouteId)
|
||||
.HasDatabaseName(
|
||||
"ix_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ve => ve.RouteId).Metadata.GetColumnName()}");
|
||||
}
|
||||
}
|
@ -0,0 +1,637 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using cuqmbr.TravelGuide.Persistence.PostgreSql;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.PostgreSql.Migrations
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlDbContext))]
|
||||
[Migration("20250504143929_Add_Vehicle_Enrollment_and_Route_Address_Detail")]
|
||||
partial class Add_Vehicle_Enrollment_and_Route_Address_Detail
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("application")
|
||||
.HasAnnotation("ProductVersion", "9.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.HasSequence("addresses_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("cities_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("countries_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("regions_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("route_address_details_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("route_addresses_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("routes_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("vehicle_enrollments_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("vehicles_id_sequence");
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.addresses_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "addresses_id_sequence");
|
||||
|
||||
b.Property<long>("CityId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("city_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<double>("Latitude")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<double>("Longitude")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(128)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(16)")
|
||||
.HasColumnName("vehicle_type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_addresses");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_addresses_uuid");
|
||||
|
||||
b.HasIndex("CityId")
|
||||
.HasDatabaseName("ix_addresses_city_id");
|
||||
|
||||
b.ToTable("addresses", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_addresses_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.cities_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "cities_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<long>("RegionId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("region_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_cities");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_cities_uuid");
|
||||
|
||||
b.HasIndex("RegionId")
|
||||
.HasDatabaseName("ix_cities_region_id");
|
||||
|
||||
b.ToTable("cities", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.countries_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "countries_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_countries");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_countries_uuid");
|
||||
|
||||
b.ToTable("countries", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.regions_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "regions_id_sequence");
|
||||
|
||||
b.Property<long>("CountryId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("country_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_regions");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_regions_uuid");
|
||||
|
||||
b.HasIndex("CountryId")
|
||||
.HasDatabaseName("ix_regions_country_id");
|
||||
|
||||
b.ToTable("regions", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.routes_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "routes_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(16)")
|
||||
.HasColumnName("vehicle_type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_routes");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_routes_uuid");
|
||||
|
||||
b.ToTable("routes", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_routes_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.route_addresses_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_addresses_id_sequence");
|
||||
|
||||
b.Property<long>("AddressId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("address_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<short>("Order")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("order");
|
||||
|
||||
b.Property<long>("RouteId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_route_addresses");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_route_addresses_uuid");
|
||||
|
||||
b.HasAlternateKey("AddressId", "RouteId", "Order")
|
||||
.HasName("altk_route_addresses_address_id_route_id_order");
|
||||
|
||||
b.HasIndex("AddressId")
|
||||
.HasDatabaseName("ix_route_addresses_address_id");
|
||||
|
||||
b.HasIndex("RouteId")
|
||||
.HasDatabaseName("ix_route_addresses_route_id");
|
||||
|
||||
b.ToTable("route_addresses", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.route_address_details_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_address_details_id_sequence");
|
||||
|
||||
b.Property<decimal>("CostToNextAddress")
|
||||
.HasColumnType("numeric(24,12)")
|
||||
.HasColumnName("cost_to_next_address");
|
||||
|
||||
b.Property<TimeSpan>("CurrentAddressStopTime")
|
||||
.HasColumnType("interval")
|
||||
.HasColumnName("current_address_stop_time");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<long>("RouteAddressId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_address_id");
|
||||
|
||||
b.Property<TimeSpan>("TimeToNextAddress")
|
||||
.HasColumnType("interval")
|
||||
.HasColumnName("time_to_next_address");
|
||||
|
||||
b.Property<long>("VehicleEnrollmentId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("vehicle_enrollment_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_route_address_details");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_route_address_details_uuid");
|
||||
|
||||
b.HasIndex("RouteAddressId")
|
||||
.HasDatabaseName("ix_route_address_details_route_address_id");
|
||||
|
||||
b.HasIndex("VehicleEnrollmentId")
|
||||
.HasDatabaseName("ix_route_address_details_vehicle_enrollment_id");
|
||||
|
||||
b.ToTable("route_address_details", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.vehicles_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "vehicles_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(16)")
|
||||
.HasColumnName("vehicle_type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_vehicles");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_vehicles_uuid");
|
||||
|
||||
b.ToTable("vehicles", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
|
||||
b.HasDiscriminator<string>("VehicleType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.vehicle_enrollments_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "vehicle_enrollments_id_sequence");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(8)")
|
||||
.HasColumnName("currency");
|
||||
|
||||
b.Property<DateTimeOffset>("DepartureTime")
|
||||
.HasColumnType("timestamptz")
|
||||
.HasColumnName("departure_time");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<long>("RouteId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_id");
|
||||
|
||||
b.Property<long>("VehicleId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("vehicle_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_vehicle_enrollments");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_vehicle_enrollments_uuid");
|
||||
|
||||
b.HasIndex("RouteId")
|
||||
.HasDatabaseName("ix_vehicle_enrollments_route_id");
|
||||
|
||||
b.HasIndex("VehicleId")
|
||||
.HasDatabaseName("ix_vehicle_enrollments_vehicle_id");
|
||||
|
||||
b.ToTable("vehicle_enrollments", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_vehicle_enrollments_currency", "currency IN ('USD', 'EUR', 'UAH')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Aircraft", b =>
|
||||
{
|
||||
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
|
||||
|
||||
b.Property<short>("Capacity")
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("capacity");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("model");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("varchar(32)")
|
||||
.HasColumnName("number");
|
||||
|
||||
b.ToTable(t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue("aircraft");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Bus", b =>
|
||||
{
|
||||
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
|
||||
|
||||
b.Property<short>("Capacity")
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("capacity");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("model");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("varchar(32)")
|
||||
.HasColumnName("number");
|
||||
|
||||
b.ToTable(t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue("bus");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Train", b =>
|
||||
{
|
||||
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
|
||||
|
||||
b.Property<short>("Capacity")
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("capacity");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("model");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnUpdateSometimes()
|
||||
.HasColumnType("varchar(32)")
|
||||
.HasColumnName("number");
|
||||
|
||||
b.ToTable(t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue("train");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.City", "City")
|
||||
.WithMany("Addresses")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_addresses_city_id");
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Region", "Region")
|
||||
.WithMany("Cities")
|
||||
.HasForeignKey("RegionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_cities_region_id");
|
||||
|
||||
b.Navigation("Region");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Country", "Country")
|
||||
.WithMany("Regions")
|
||||
.HasForeignKey("CountryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_regions_country_id");
|
||||
|
||||
b.Navigation("Country");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Address", "Address")
|
||||
.WithMany("AddressRoutes")
|
||||
.HasForeignKey("AddressId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_addresses_address_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
|
||||
.WithMany("RouteAddresses")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_addresses_route_id");
|
||||
|
||||
b.Navigation("Address");
|
||||
|
||||
b.Navigation("Route");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", "RouteAddress")
|
||||
.WithMany("Details")
|
||||
.HasForeignKey("RouteAddressId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_address_details_route_address_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment")
|
||||
.WithMany("RouteAddressDetails")
|
||||
.HasForeignKey("VehicleEnrollmentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_address_details_vehicle_enrollment_id");
|
||||
|
||||
b.Navigation("RouteAddress");
|
||||
|
||||
b.Navigation("VehicleEnrollment");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
|
||||
.WithMany("VehicleEnrollments")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_vehicle_enrollments_route_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Vehicle", "Vehicle")
|
||||
.WithMany("Enrollments")
|
||||
.HasForeignKey("VehicleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_vehicle_enrollments_vehicle_id");
|
||||
|
||||
b.Navigation("Route");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Navigation("AddressRoutes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("Addresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b =>
|
||||
{
|
||||
b.Navigation("Regions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
|
||||
{
|
||||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Navigation("RouteAddresses");
|
||||
|
||||
b.Navigation("VehicleEnrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.Navigation("Details");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
|
||||
{
|
||||
b.Navigation("Enrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
|
||||
{
|
||||
b.Navigation("RouteAddressDetails");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.PostgreSql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_Vehicle_Enrollment_and_Route_Address_Detail : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateSequence(
|
||||
name: "route_address_details_id_sequence",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.CreateSequence(
|
||||
name: "vehicle_enrollments_id_sequence",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "vehicle_enrollments",
|
||||
schema: "application",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.vehicle_enrollments_id_sequence')"),
|
||||
departure_time = table.Column<DateTimeOffset>(type: "timestamptz", nullable: false),
|
||||
currency = table.Column<string>(type: "varchar(8)", nullable: false),
|
||||
vehicle_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
route_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
uuid = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_vehicle_enrollments", x => x.id);
|
||||
table.UniqueConstraint("altk_vehicle_enrollments_uuid", x => x.uuid);
|
||||
table.CheckConstraint("ck_vehicle_enrollments_currency", "currency IN ('USD', 'EUR', 'UAH')");
|
||||
table.ForeignKey(
|
||||
name: "fk_vehicle_enrollments_route_id",
|
||||
column: x => x.route_id,
|
||||
principalSchema: "application",
|
||||
principalTable: "routes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_vehicle_enrollments_vehicle_id",
|
||||
column: x => x.vehicle_id,
|
||||
principalSchema: "application",
|
||||
principalTable: "vehicles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "route_address_details",
|
||||
schema: "application",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.route_address_details_id_sequence')"),
|
||||
time_to_next_address = table.Column<TimeSpan>(type: "interval", nullable: false),
|
||||
cost_to_next_address = table.Column<decimal>(type: "numeric(24,12)", nullable: false),
|
||||
current_address_stop_time = table.Column<TimeSpan>(type: "interval", nullable: false),
|
||||
vehicle_enrollment_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
route_address_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
uuid = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_route_address_details", x => x.id);
|
||||
table.UniqueConstraint("altk_route_address_details_uuid", x => x.uuid);
|
||||
table.ForeignKey(
|
||||
name: "fk_route_address_details_route_address_id",
|
||||
column: x => x.route_address_id,
|
||||
principalSchema: "application",
|
||||
principalTable: "route_addresses",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_route_address_details_vehicle_enrollment_id",
|
||||
column: x => x.vehicle_enrollment_id,
|
||||
principalSchema: "application",
|
||||
principalTable: "vehicle_enrollments",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_route_address_details_route_address_id",
|
||||
schema: "application",
|
||||
table: "route_address_details",
|
||||
column: "route_address_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_route_address_details_vehicle_enrollment_id",
|
||||
schema: "application",
|
||||
table: "route_address_details",
|
||||
column: "vehicle_enrollment_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_vehicle_enrollments_route_id",
|
||||
schema: "application",
|
||||
table: "vehicle_enrollments",
|
||||
column: "route_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_vehicle_enrollments_vehicle_id",
|
||||
schema: "application",
|
||||
table: "vehicle_enrollments",
|
||||
column: "vehicle_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "route_address_details",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "vehicle_enrollments",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.DropSequence(
|
||||
name: "route_address_details_id_sequence",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.DropSequence(
|
||||
name: "vehicle_enrollments_id_sequence",
|
||||
schema: "application");
|
||||
}
|
||||
}
|
||||
}
|
@ -31,10 +31,14 @@ namespace Persistence.PostgreSql.Migrations
|
||||
|
||||
modelBuilder.HasSequence("regions_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("route_address_details_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("route_addresses_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("routes_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("vehicle_enrollments_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("vehicles_id_sequence");
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
@ -264,6 +268,55 @@ namespace Persistence.PostgreSql.Migrations
|
||||
b.ToTable("route_addresses", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.route_address_details_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_address_details_id_sequence");
|
||||
|
||||
b.Property<decimal>("CostToNextAddress")
|
||||
.HasColumnType("numeric(24,12)")
|
||||
.HasColumnName("cost_to_next_address");
|
||||
|
||||
b.Property<TimeSpan>("CurrentAddressStopTime")
|
||||
.HasColumnType("interval")
|
||||
.HasColumnName("current_address_stop_time");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<long>("RouteAddressId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_address_id");
|
||||
|
||||
b.Property<TimeSpan>("TimeToNextAddress")
|
||||
.HasColumnType("interval")
|
||||
.HasColumnName("time_to_next_address");
|
||||
|
||||
b.Property<long>("VehicleEnrollmentId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("vehicle_enrollment_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_route_address_details");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_route_address_details_uuid");
|
||||
|
||||
b.HasIndex("RouteAddressId")
|
||||
.HasDatabaseName("ix_route_address_details_route_address_id");
|
||||
|
||||
b.HasIndex("VehicleEnrollmentId")
|
||||
.HasDatabaseName("ix_route_address_details_vehicle_enrollment_id");
|
||||
|
||||
b.ToTable("route_address_details", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@ -299,6 +352,55 @@ namespace Persistence.PostgreSql.Migrations
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.vehicle_enrollments_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "vehicle_enrollments_id_sequence");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(8)")
|
||||
.HasColumnName("currency");
|
||||
|
||||
b.Property<DateTimeOffset>("DepartureTime")
|
||||
.HasColumnType("timestamptz")
|
||||
.HasColumnName("departure_time");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<long>("RouteId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_id");
|
||||
|
||||
b.Property<long>("VehicleId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("vehicle_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_vehicle_enrollments");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_vehicle_enrollments_uuid");
|
||||
|
||||
b.HasIndex("RouteId")
|
||||
.HasDatabaseName("ix_vehicle_enrollments_route_id");
|
||||
|
||||
b.HasIndex("VehicleId")
|
||||
.HasDatabaseName("ix_vehicle_enrollments_vehicle_id");
|
||||
|
||||
b.ToTable("vehicle_enrollments", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_vehicle_enrollments_currency", "currency IN ('USD', 'EUR', 'UAH')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Aircraft", b =>
|
||||
{
|
||||
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
|
||||
@ -443,6 +545,48 @@ namespace Persistence.PostgreSql.Migrations
|
||||
b.Navigation("Route");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", "RouteAddress")
|
||||
.WithMany("Details")
|
||||
.HasForeignKey("RouteAddressId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_address_details_route_address_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment")
|
||||
.WithMany("RouteAddressDetails")
|
||||
.HasForeignKey("VehicleEnrollmentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_address_details_vehicle_enrollment_id");
|
||||
|
||||
b.Navigation("RouteAddress");
|
||||
|
||||
b.Navigation("VehicleEnrollment");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
|
||||
.WithMany("VehicleEnrollments")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_vehicle_enrollments_route_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Vehicle", "Vehicle")
|
||||
.WithMany("Enrollments")
|
||||
.HasForeignKey("VehicleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_vehicle_enrollments_vehicle_id");
|
||||
|
||||
b.Navigation("Route");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Navigation("AddressRoutes");
|
||||
@ -466,6 +610,23 @@ namespace Persistence.PostgreSql.Migrations
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Navigation("RouteAddresses");
|
||||
|
||||
b.Navigation("VehicleEnrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.Navigation("Details");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
|
||||
{
|
||||
b.Navigation("Enrollments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
|
||||
{
|
||||
b.Navigation("RouteAddressDetails");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
@ -36,7 +36,16 @@ public class PostgreSqlDbContext : DbContext
|
||||
{
|
||||
builder
|
||||
.Properties<VehicleType>()
|
||||
.HaveColumnType("vehicle_type")
|
||||
.HaveColumnType("varchar(16)")
|
||||
.HaveConversion<VehicleTypeConverter>();
|
||||
|
||||
builder
|
||||
.Properties<Currency>()
|
||||
.HaveColumnType("varchar(8)")
|
||||
.HaveConversion<CurrencyConverter>();
|
||||
|
||||
builder
|
||||
.Properties<DateTimeOffset>()
|
||||
.HaveConversion<DateTimeOffsetConverter>();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
|
||||
BusRepository = new PostgreSqlBusRepository(_dbContext);
|
||||
AircraftRepository = new PostgreSqlAircraftRepository(_dbContext);
|
||||
TrainRepository = new PostgreSqlTrainRepository(_dbContext);
|
||||
VehicleEnrollmentRepository =
|
||||
new PostgreSqlVehicleEnrollmentRepository(_dbContext);
|
||||
RouteAddressRepository =
|
||||
new PostgreSqlRouteAddressRepository(_dbContext);
|
||||
}
|
||||
|
||||
public CountryRepository CountryRepository { get; init; }
|
||||
@ -42,6 +46,10 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
|
||||
|
||||
public TrainRepository TrainRepository { get; init; }
|
||||
|
||||
public VehicleEnrollmentRepository VehicleEnrollmentRepository { get; init; }
|
||||
|
||||
public RouteAddressRepository RouteAddressRepository { get; init; }
|
||||
|
||||
public int Save()
|
||||
{
|
||||
return _dbContext.SaveChanges();
|
||||
|
@ -0,0 +1,11 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Repositories;
|
||||
|
||||
public sealed class PostgreSqlRouteAddressRepository :
|
||||
PostgreSqlBaseRepository<RouteAddress>, RouteAddressRepository
|
||||
{
|
||||
public PostgreSqlRouteAddressRepository(PostgreSqlDbContext dbContext)
|
||||
: base(dbContext) { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Repositories;
|
||||
|
||||
public sealed class PostgreSqlVehicleEnrollmentRepository :
|
||||
PostgreSqlBaseRepository<VehicleEnrollment>, VehicleEnrollmentRepository
|
||||
{
|
||||
public PostgreSqlVehicleEnrollmentRepository(PostgreSqlDbContext dbContext)
|
||||
: base(dbContext) { }
|
||||
}
|
13
src/Persistence/TypeConverters/CurrencyConverter.cs
Normal file
13
src/Persistence/TypeConverters/CurrencyConverter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.TypeConverters;
|
||||
|
||||
public class CurrencyConverter : ValueConverter<Currency, string>
|
||||
{
|
||||
public CurrencyConverter()
|
||||
: base(
|
||||
v => v.Name,
|
||||
v => Currency.FromName(v))
|
||||
{ }
|
||||
}
|
15
src/Persistence/TypeConverters/DateTimeOffsetConverter.cs
Normal file
15
src/Persistence/TypeConverters/DateTimeOffsetConverter.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.TypeConverters;
|
||||
|
||||
// Convert localized time to UTC
|
||||
|
||||
public class DateTimeOffsetConverter :
|
||||
ValueConverter<DateTimeOffset, DateTimeOffset>
|
||||
{
|
||||
public DateTimeOffsetConverter()
|
||||
: base(
|
||||
v => v.ToUniversalTime(),
|
||||
v => v)
|
||||
{ }
|
||||
}
|
Loading…
Reference in New Issue
Block a user