add shortest vehicle enrollment with transfers search
This commit is contained in:
parent
6830fea563
commit
d5ffedbdb9
@ -17,6 +17,7 @@
|
|||||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
<PackageReference Include="MediatR.Behaviors.Authorization" Version="12.2.0" />
|
<PackageReference Include="MediatR.Behaviors.Authorization" Version="12.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||||
|
<PackageReference Include="QuikGraph" Version="2.5.0" />
|
||||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.2" />
|
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -10,15 +10,15 @@ public sealed class RouteAddressDto : IMapFrom<RouteAddress>
|
|||||||
public short Order { get; set; }
|
public short Order { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public Guid AddressUuid { get; set; }
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
public string AddressName { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public double AddressLongitude { get; set; }
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
public double AddressLatitude { get; set; }
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
public string AddressVehicleType { get; set; }
|
public string VehicleType { get; set; }
|
||||||
|
|
||||||
public Guid CountryUuid { get; set; }
|
public Guid CountryUuid { get; set; }
|
||||||
|
|
||||||
@ -39,19 +39,19 @@ public sealed class RouteAddressDto : IMapFrom<RouteAddress>
|
|||||||
d => d.RouteAddressUuid,
|
d => d.RouteAddressUuid,
|
||||||
opt => opt.MapFrom(s => s.Guid))
|
opt => opt.MapFrom(s => s.Guid))
|
||||||
.ForMember(
|
.ForMember(
|
||||||
d => d.AddressUuid,
|
d => d.Uuid,
|
||||||
opt => opt.MapFrom(s => s.Address.Guid))
|
opt => opt.MapFrom(s => s.Address.Guid))
|
||||||
.ForMember(
|
.ForMember(
|
||||||
d => d.AddressName,
|
d => d.Name,
|
||||||
opt => opt.MapFrom(s => s.Address.Name))
|
opt => opt.MapFrom(s => s.Address.Name))
|
||||||
.ForMember(
|
.ForMember(
|
||||||
d => d.AddressLongitude,
|
d => d.Longitude,
|
||||||
opt => opt.MapFrom(s => s.Address.Longitude))
|
opt => opt.MapFrom(s => s.Address.Longitude))
|
||||||
.ForMember(
|
.ForMember(
|
||||||
d => d.AddressLatitude,
|
d => d.Latitude,
|
||||||
opt => opt.MapFrom(s => s.Address.Latitude))
|
opt => opt.MapFrom(s => s.Address.Latitude))
|
||||||
.ForMember(
|
.ForMember(
|
||||||
d => d.AddressVehicleType,
|
d => d.VehicleType,
|
||||||
opt => opt.MapFrom(s => s.Address.VehicleType.Name))
|
opt => opt.MapFrom(s => s.Address.VehicleType.Name))
|
||||||
.ForMember(
|
.ForMember(
|
||||||
d => d.CityUuid,
|
d => d.CityUuid,
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.VehicleEnrollmentSearch.Queries.SearchShortest;
|
||||||
|
|
||||||
|
public record SearchShortestQuery :
|
||||||
|
IRequest<VehicleEnrollmentSearchDto>
|
||||||
|
{
|
||||||
|
public Guid DepartureAddressGuid { get; set; }
|
||||||
|
|
||||||
|
public Guid ArrivalAddressGuid { get; set; }
|
||||||
|
|
||||||
|
public DateOnly DepartureDate { get; set; }
|
||||||
|
|
||||||
|
public HashSet<VehicleType> VehicleTypes { get; set; }
|
||||||
|
|
||||||
|
public bool ShortestByCost { get; set; }
|
||||||
|
|
||||||
|
public bool ShortestByTime { 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
|
||||||
|
.VehicleEnrollmentSearch.Queries.SearchShortest;
|
||||||
|
|
||||||
|
public class SearchShortestQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<SearchShortestQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public SearchShortestQueryAuthorizer(
|
||||||
|
SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(SearchShortestQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,436 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using AutoMapper;
|
||||||
|
using QuikGraph;
|
||||||
|
using QuikGraph.Algorithms;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.VehicleEnrollmentSearch.Queries.SearchShortest;
|
||||||
|
|
||||||
|
// TODO: Refactor.
|
||||||
|
// TODO: Add configurable time between transfers.
|
||||||
|
public class SearchShortestQueryHandler :
|
||||||
|
IRequestHandler<SearchShortestQuery, VehicleEnrollmentSearchDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
private readonly SessionCurrencyService _sessionCurrencyService;
|
||||||
|
private readonly CurrencyConverterService _currencyConverterService;
|
||||||
|
|
||||||
|
public SearchShortestQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper,
|
||||||
|
SessionCurrencyService sessionCurrencyService,
|
||||||
|
CurrencyConverterService currencyConverterService)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
_sessionCurrencyService = sessionCurrencyService;
|
||||||
|
_currencyConverterService = currencyConverterService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VehicleEnrollmentSearchDto> Handle(
|
||||||
|
SearchShortestQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Get related data
|
||||||
|
|
||||||
|
var zeroTime = TimeOnly.FromTimeSpan(TimeSpan.Zero);
|
||||||
|
var departureDate =
|
||||||
|
new DateTimeOffset(request.DepartureDate, zeroTime, TimeSpan.Zero);
|
||||||
|
|
||||||
|
var range = TimeSpan.FromDays(3);
|
||||||
|
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
e =>
|
||||||
|
e.DepartureTime >= departureDate.Subtract(range) &&
|
||||||
|
e.DepartureTime <= departureDate.Add(range) &&
|
||||||
|
request.VehicleTypes.Contains(e.Vehicle.VehicleType),
|
||||||
|
e => e.Route,
|
||||||
|
1, int.MaxValue, cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
if (vehicleEnrollments.Count == 0)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var vehicleEnrollmentIds = vehicleEnrollments.Select(e => e.Id);
|
||||||
|
var routeAddressDetails = (await _unitOfWork.RouteAddressDetailRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
e => vehicleEnrollmentIds.Contains(e.VehicleEnrollmentId),
|
||||||
|
e => e.RouteAddress.Address.City.Region.Country,
|
||||||
|
1, int.MaxValue, cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
|
||||||
|
// Hydrate vehicle enrollments with route address details
|
||||||
|
|
||||||
|
foreach (var vehicleEnrollment in vehicleEnrollments)
|
||||||
|
{
|
||||||
|
vehicleEnrollment.RouteAddressDetails = routeAddressDetails
|
||||||
|
.Where(e => e.VehicleEnrollmentId == vehicleEnrollment.Id)
|
||||||
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Creat and fill graph data structure
|
||||||
|
|
||||||
|
var graph = new AdjacencyGraph<
|
||||||
|
Address, TaggedEdge<Address, RouteAddressDetail>>();
|
||||||
|
|
||||||
|
foreach (var vehicleEnrollment in vehicleEnrollments)
|
||||||
|
{
|
||||||
|
var vehicleEnrollmentRouteAddressDetails = routeAddressDetails
|
||||||
|
.Where(e => e.VehicleEnrollmentId == vehicleEnrollment.Id)
|
||||||
|
.OrderBy(e => e.RouteAddress.Order);
|
||||||
|
|
||||||
|
for (int i = 1; i < vehicleEnrollmentRouteAddressDetails.Count(); i++)
|
||||||
|
{
|
||||||
|
var sourceRouteAddressDetail =
|
||||||
|
vehicleEnrollmentRouteAddressDetails.ElementAt(i-1);
|
||||||
|
var targetRouteAddressDetail =
|
||||||
|
vehicleEnrollmentRouteAddressDetails.ElementAt(i);
|
||||||
|
|
||||||
|
var sourceAddress =
|
||||||
|
sourceRouteAddressDetail.RouteAddress.Address;
|
||||||
|
var targetAddress =
|
||||||
|
targetRouteAddressDetail.RouteAddress.Address;
|
||||||
|
|
||||||
|
var weight = sourceRouteAddressDetail.CostToNextAddress;
|
||||||
|
|
||||||
|
graph.AddVerticesAndEdge(
|
||||||
|
new TaggedEdge<Address, RouteAddressDetail>(
|
||||||
|
sourceAddress, targetAddress, sourceRouteAddressDetail));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Find paths
|
||||||
|
|
||||||
|
var departureAddress = routeAddressDetails
|
||||||
|
.First(e => e.RouteAddress.Address.Guid == request.DepartureAddressGuid)
|
||||||
|
.RouteAddress.Address;
|
||||||
|
var arrivalAddress = routeAddressDetails
|
||||||
|
.First(e => e.RouteAddress.Address.Guid == request.ArrivalAddressGuid)
|
||||||
|
.RouteAddress.Address;
|
||||||
|
|
||||||
|
|
||||||
|
Func<TaggedEdge<Address, RouteAddressDetail>, double> weightByCost =
|
||||||
|
edge => (double)edge.Tag.CostToNextAddress;
|
||||||
|
|
||||||
|
Func<TaggedEdge<Address, RouteAddressDetail>, double> weightByTime =
|
||||||
|
edge =>
|
||||||
|
edge.Tag.TimeToNextAddress.Ticks +
|
||||||
|
edge.Tag.CurrentAddressStopTime.Ticks;
|
||||||
|
|
||||||
|
Func<TaggedEdge<Address, RouteAddressDetail>, double> edgeWeight =
|
||||||
|
_ => 0;
|
||||||
|
|
||||||
|
if (request.ShortestByCost && request.ShortestByTime)
|
||||||
|
{
|
||||||
|
edgeWeight = edge => weightByCost(edge) + weightByTime(edge);
|
||||||
|
}
|
||||||
|
else if (request.ShortestByCost)
|
||||||
|
{
|
||||||
|
edgeWeight = edge => weightByCost(edge);
|
||||||
|
}
|
||||||
|
else if (request.ShortestByTime)
|
||||||
|
{
|
||||||
|
edgeWeight = edge => weightByTime(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var tryGetPaths = graph.ShortestPathsDijkstra(edgeWeight, departureAddress);
|
||||||
|
|
||||||
|
|
||||||
|
// Create and hydrate a DTO object
|
||||||
|
|
||||||
|
var vehicleEnrollmentDtos =
|
||||||
|
new List<VehicleEnrollmentSearchVehicleEnrollmentDto>();
|
||||||
|
|
||||||
|
var totalTravelTime = TimeSpan.Zero;
|
||||||
|
var totalCost = (decimal)0;
|
||||||
|
|
||||||
|
if (tryGetPaths(arrivalAddress, out var path))
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = path.First().Tag.RouteAddressId;
|
||||||
|
long lastRouteAddressId;
|
||||||
|
Guid lastRouteAddressGuid;
|
||||||
|
|
||||||
|
var addressDtos = new List<VehicleEnrollmentSearchAddressDto>();
|
||||||
|
var addressOrder = (short)1;
|
||||||
|
var enrollmentTravelTime = TimeSpan.Zero;
|
||||||
|
var enrollmentCost = (decimal)0;
|
||||||
|
var enrollmentOrder = (short)1;
|
||||||
|
|
||||||
|
Address source;
|
||||||
|
Address target;
|
||||||
|
RouteAddressDetail tag;
|
||||||
|
RouteAddressDetail nextTag;
|
||||||
|
|
||||||
|
for (int i = 0; i < path.Count() - 1; i++)
|
||||||
|
{
|
||||||
|
source = path.Select(e => e.Source).ElementAt(i);
|
||||||
|
tag = path.Select(e => e.Tag).ElementAt(i);
|
||||||
|
nextTag = path.Select(e => e.Tag).ElementAt(i+1);
|
||||||
|
|
||||||
|
|
||||||
|
totalTravelTime +=
|
||||||
|
tag.TimeToNextAddress + tag.CurrentAddressStopTime;
|
||||||
|
enrollmentTravelTime +=
|
||||||
|
tag.TimeToNextAddress + tag.CurrentAddressStopTime;
|
||||||
|
|
||||||
|
totalCost += tag.CostToNextAddress;
|
||||||
|
enrollmentCost += tag.CostToNextAddress;
|
||||||
|
|
||||||
|
addressDtos.Add(new VehicleEnrollmentSearchAddressDto()
|
||||||
|
{
|
||||||
|
Uuid = source.Guid,
|
||||||
|
Name = source.Name,
|
||||||
|
Longitude = source.Latitude,
|
||||||
|
Latitude = source.Longitude,
|
||||||
|
CountryUuid = source.City.Region.Country.Guid,
|
||||||
|
CountryName = source.City.Region.Country.Name,
|
||||||
|
RegionUuid = source.City.Region.Guid,
|
||||||
|
RegionName = source.City.Region.Name,
|
||||||
|
CityUuid = source.City.Guid,
|
||||||
|
CityName = source.City.Name,
|
||||||
|
TimeToNextAddress = tag.TimeToNextAddress,
|
||||||
|
CostToNextAddress = tag.CostToNextAddress,
|
||||||
|
CurrentAddressStopTime = tag.CurrentAddressStopTime,
|
||||||
|
Order = addressOrder,
|
||||||
|
RouteAddressUuid = tag.RouteAddress.Guid
|
||||||
|
});
|
||||||
|
|
||||||
|
addressOrder++;
|
||||||
|
|
||||||
|
|
||||||
|
// First address after transfer
|
||||||
|
if (nextTag.VehicleEnrollmentId != tag.VehicleEnrollmentId)
|
||||||
|
{
|
||||||
|
target = path.Select(e => e.Target).ElementAt(i);
|
||||||
|
|
||||||
|
lastRouteAddressGuid = vehicleEnrollments
|
||||||
|
.Single(e => e.Id == tag.VehicleEnrollmentId)
|
||||||
|
.RouteAddressDetails
|
||||||
|
.Select(e => e.RouteAddress)
|
||||||
|
.OrderBy(e => e.Order)
|
||||||
|
.SkipWhile(e => e.Order != tag.RouteAddress.Order)
|
||||||
|
.Take(2)
|
||||||
|
.ElementAt(1)
|
||||||
|
.Guid;
|
||||||
|
|
||||||
|
addressDtos.Add(new VehicleEnrollmentSearchAddressDto()
|
||||||
|
{
|
||||||
|
Uuid = target.Guid,
|
||||||
|
Name = target.Name,
|
||||||
|
Longitude = target.Latitude,
|
||||||
|
Latitude = target.Longitude,
|
||||||
|
CountryUuid = target.City.Region.Country.Guid,
|
||||||
|
CountryName = target.City.Region.Country.Name,
|
||||||
|
RegionUuid = target.City.Region.Guid,
|
||||||
|
RegionName = target.City.Region.Name,
|
||||||
|
CityUuid = target.City.Guid,
|
||||||
|
CityName = target.City.Name,
|
||||||
|
TimeToNextAddress = TimeSpan.Zero,
|
||||||
|
CostToNextAddress = 0,
|
||||||
|
CurrentAddressStopTime = TimeSpan.Zero,
|
||||||
|
Order = addressOrder,
|
||||||
|
RouteAddressUuid = lastRouteAddressGuid
|
||||||
|
});
|
||||||
|
|
||||||
|
lastRouteAddressId = vehicleEnrollments
|
||||||
|
.Single(e => e.Id == tag.VehicleEnrollmentId)
|
||||||
|
.RouteAddressDetails
|
||||||
|
.Select(e => e.RouteAddress)
|
||||||
|
.OrderBy(e => e.Order)
|
||||||
|
.SkipWhile(e => e.Order != tag.RouteAddress.Order)
|
||||||
|
.Take(2)
|
||||||
|
.ElementAt(1)
|
||||||
|
.Id;
|
||||||
|
|
||||||
|
vehicleEnrollmentDtos.Add(
|
||||||
|
new VehicleEnrollmentSearchVehicleEnrollmentDto()
|
||||||
|
{
|
||||||
|
DepartureTime = tag.VehicleEnrollment
|
||||||
|
.GetDepartureTime(firstRouteAddressId),
|
||||||
|
ArrivalTime = tag.VehicleEnrollment
|
||||||
|
.GetArrivalTime(lastRouteAddressId),
|
||||||
|
TravelTime = tag.VehicleEnrollment
|
||||||
|
.GetTravelTime(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
TimeMoving = tag.VehicleEnrollment
|
||||||
|
.GetTimeMoving(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
TimeInStops = tag.VehicleEnrollment
|
||||||
|
.GetTimeInStops(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
NumberOfStops = tag.VehicleEnrollment
|
||||||
|
.GetNumberOfStops(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
Currency = tag.VehicleEnrollment.Currency.Name,
|
||||||
|
Cost = tag.VehicleEnrollment
|
||||||
|
.GetCost(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
VehicleType = source.VehicleType.Name,
|
||||||
|
Uuid = tag.VehicleEnrollment.Guid,
|
||||||
|
Order = enrollmentOrder,
|
||||||
|
Addresses = addressDtos
|
||||||
|
});
|
||||||
|
|
||||||
|
firstRouteAddressId = nextTag.RouteAddressId;
|
||||||
|
|
||||||
|
addressDtos = new List<VehicleEnrollmentSearchAddressDto>();
|
||||||
|
addressOrder = (short)1;
|
||||||
|
enrollmentTravelTime = TimeSpan.Zero;
|
||||||
|
enrollmentCost = (decimal)0;
|
||||||
|
enrollmentOrder++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source = path.Select(e => e.Source).Last();
|
||||||
|
target = path.Select(e => e.Target).Last();
|
||||||
|
tag = path.Select(e => e.Tag).Last();
|
||||||
|
nextTag = path.Select(e => e.Tag).Last();
|
||||||
|
|
||||||
|
addressDtos.Add(new VehicleEnrollmentSearchAddressDto()
|
||||||
|
{
|
||||||
|
Uuid = source.Guid,
|
||||||
|
Name = source.Name,
|
||||||
|
Longitude = source.Latitude,
|
||||||
|
Latitude = source.Longitude,
|
||||||
|
CountryUuid = source.City.Region.Country.Guid,
|
||||||
|
CountryName = source.City.Region.Country.Name,
|
||||||
|
RegionUuid = source.City.Region.Guid,
|
||||||
|
RegionName = source.City.Region.Name,
|
||||||
|
CityUuid = source.City.Guid,
|
||||||
|
CityName = source.City.Name,
|
||||||
|
TimeToNextAddress = tag.TimeToNextAddress,
|
||||||
|
CostToNextAddress = tag.CostToNextAddress,
|
||||||
|
CurrentAddressStopTime = tag.CurrentAddressStopTime,
|
||||||
|
Order = addressOrder,
|
||||||
|
RouteAddressUuid = tag.RouteAddress.Guid
|
||||||
|
});
|
||||||
|
|
||||||
|
lastRouteAddressGuid = vehicleEnrollments
|
||||||
|
.Single(e => e.Id == tag.VehicleEnrollmentId)
|
||||||
|
.RouteAddressDetails
|
||||||
|
.Select(e => e.RouteAddress)
|
||||||
|
.OrderBy(e => e.Order)
|
||||||
|
.SkipWhile(e => e.Order != tag.RouteAddress.Order)
|
||||||
|
.Take(2)
|
||||||
|
.ElementAt(1)
|
||||||
|
.Guid;
|
||||||
|
|
||||||
|
addressOrder++;
|
||||||
|
addressDtos.Add(new VehicleEnrollmentSearchAddressDto()
|
||||||
|
{
|
||||||
|
Uuid = target.Guid,
|
||||||
|
Name = target.Name,
|
||||||
|
Longitude = target.Latitude,
|
||||||
|
Latitude = target.Longitude,
|
||||||
|
CountryUuid = target.City.Region.Country.Guid,
|
||||||
|
CountryName = target.City.Region.Country.Name,
|
||||||
|
RegionUuid = target.City.Region.Guid,
|
||||||
|
RegionName = target.City.Region.Name,
|
||||||
|
CityUuid = target.City.Guid,
|
||||||
|
CityName = target.City.Name,
|
||||||
|
TimeToNextAddress = TimeSpan.Zero,
|
||||||
|
CostToNextAddress = 0,
|
||||||
|
CurrentAddressStopTime = TimeSpan.Zero,
|
||||||
|
Order = addressOrder,
|
||||||
|
RouteAddressUuid = lastRouteAddressGuid
|
||||||
|
});
|
||||||
|
|
||||||
|
lastRouteAddressId = vehicleEnrollments
|
||||||
|
.Single(e => e.Id == tag.VehicleEnrollmentId)
|
||||||
|
.RouteAddressDetails
|
||||||
|
.Select(e => e.RouteAddress)
|
||||||
|
.OrderBy(e => e.Order)
|
||||||
|
.SkipWhile(e => e.Order != tag.RouteAddress.Order)
|
||||||
|
.Take(2)
|
||||||
|
.ElementAt(1)
|
||||||
|
.Id;
|
||||||
|
|
||||||
|
vehicleEnrollmentDtos.Add(
|
||||||
|
new VehicleEnrollmentSearchVehicleEnrollmentDto()
|
||||||
|
{
|
||||||
|
DepartureTime = tag.VehicleEnrollment
|
||||||
|
.GetDepartureTime(firstRouteAddressId),
|
||||||
|
ArrivalTime = tag.VehicleEnrollment
|
||||||
|
.GetArrivalTime(lastRouteAddressId),
|
||||||
|
TravelTime = tag.VehicleEnrollment
|
||||||
|
.GetTravelTime(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
TimeMoving = tag.VehicleEnrollment
|
||||||
|
.GetTimeMoving(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
TimeInStops = tag.VehicleEnrollment
|
||||||
|
.GetTimeInStops(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
NumberOfStops = tag.VehicleEnrollment
|
||||||
|
.GetNumberOfStops(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
Currency = tag.VehicleEnrollment.Currency.Name,
|
||||||
|
Cost = tag.VehicleEnrollment
|
||||||
|
.GetCost(firstRouteAddressId,
|
||||||
|
lastRouteAddressId),
|
||||||
|
VehicleType = source.VehicleType.Name,
|
||||||
|
Uuid = tag.VehicleEnrollment.Guid,
|
||||||
|
Order = enrollmentOrder,
|
||||||
|
Addresses = addressDtos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var vehicleEnrollmentDto in vehicleEnrollmentDtos)
|
||||||
|
{
|
||||||
|
foreach (var addressDto in vehicleEnrollmentDto.Addresses)
|
||||||
|
{
|
||||||
|
addressDto.CostToNextAddress = await _currencyConverterService
|
||||||
|
.ConvertAsync(addressDto.CostToNextAddress,
|
||||||
|
vehicleEnrollments
|
||||||
|
.First(e => e.Guid == vehicleEnrollmentDto.Uuid)
|
||||||
|
.Currency,
|
||||||
|
_sessionCurrencyService.Currency, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicleEnrollmentDto.Currency = vehicleEnrollmentDto.Currency;
|
||||||
|
vehicleEnrollmentDto.Cost = vehicleEnrollmentDto.Addresses
|
||||||
|
.Aggregate((decimal)0,
|
||||||
|
(sum, next) => sum += next.CostToNextAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cost = vehicleEnrollmentDtos
|
||||||
|
.Aggregate((decimal)0,
|
||||||
|
(sum, next) => sum += next.Cost);
|
||||||
|
|
||||||
|
var departureTime = vehicleEnrollmentDtos
|
||||||
|
.OrderBy(e => e.Order).First().DepartureTime;
|
||||||
|
var arrivalTime = vehicleEnrollmentDtos
|
||||||
|
.OrderBy(e => e.Order).Last().ArrivalTime;
|
||||||
|
var timeInStops = vehicleEnrollmentDtos
|
||||||
|
.Aggregate(TimeSpan.Zero, (sum, next) => sum += next.TimeInStops);
|
||||||
|
var numberOfTransfers = vehicleEnrollmentDtos.Count() - 1;
|
||||||
|
|
||||||
|
return new VehicleEnrollmentSearchDto()
|
||||||
|
{
|
||||||
|
DepartureTime = departureTime,
|
||||||
|
ArrivalTime = arrivalTime,
|
||||||
|
TravelTime = arrivalTime - departureTime,
|
||||||
|
TimeInStops = timeInStops,
|
||||||
|
NumberOfTransfers = numberOfTransfers,
|
||||||
|
Enrollments = vehicleEnrollmentDtos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.VehicleEnrollmentSearch.Queries.SearchShortest;
|
||||||
|
|
||||||
|
public class SearchShortestQueryValidator :
|
||||||
|
AbstractValidator<SearchShortestQuery>
|
||||||
|
{
|
||||||
|
public SearchShortestQueryValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.DepartureAddressGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.ArrivalAddressGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.DepartureDate)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.GreaterThanOrEqualTo(DateOnly.FromDateTime(DateTime.UtcNow))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
DateOnly.FromDateTime(DateTime.UtcNow)));
|
||||||
|
|
||||||
|
RuleForEach(v => v.VehicleTypes)
|
||||||
|
.Must((v, vt) => VehicleType.Enumerations.ContainsValue(vt))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
VehicleType.Enumerations.Values.Select(e => e.Name))));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch;
|
||||||
|
|
||||||
|
public sealed class VehicleEnrollmentSearchAddressDto
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { 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 TimeSpan TimeToNextAddress { get; set; }
|
||||||
|
|
||||||
|
public decimal CostToNextAddress { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan CurrentAddressStopTime { get; set; }
|
||||||
|
|
||||||
|
public short Order { get; set; }
|
||||||
|
|
||||||
|
public Guid RouteAddressUuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch;
|
||||||
|
|
||||||
|
public sealed class VehicleEnrollmentSearchDto
|
||||||
|
{
|
||||||
|
public DateTimeOffset DepartureTime { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset ArrivalTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TravelTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeInStops { get; set; }
|
||||||
|
|
||||||
|
public int NumberOfTransfers { get; set; }
|
||||||
|
|
||||||
|
public ICollection<VehicleEnrollmentSearchVehicleEnrollmentDto>
|
||||||
|
Enrollments { get; set; }
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch;
|
||||||
|
|
||||||
|
public sealed class VehicleEnrollmentSearchVehicleEnrollmentDto
|
||||||
|
{
|
||||||
|
public string VehicleType { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset DepartureTime { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset ArrivalTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TravelTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeMoving { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeInStops { get; set; }
|
||||||
|
|
||||||
|
public int NumberOfStops { get; set; }
|
||||||
|
|
||||||
|
public string Currency { get; set; }
|
||||||
|
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public short Order { get; set; }
|
||||||
|
|
||||||
|
public ICollection<VehicleEnrollmentSearchAddressDto>
|
||||||
|
Addresses { get; set; }
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch.ViewModels;
|
||||||
|
|
||||||
|
public sealed class SearchShortestViewModel
|
||||||
|
{
|
||||||
|
public Guid DepartureAddressUuid { get; set; }
|
||||||
|
|
||||||
|
public Guid ArrivalAddressUuid { get; set; }
|
||||||
|
|
||||||
|
public DateOnly DepartureDate { get; set; }
|
||||||
|
|
||||||
|
public HashSet<string> VehicleTypes { get; set; }
|
||||||
|
|
||||||
|
public bool ShortestByCost { get; set; }
|
||||||
|
|
||||||
|
public bool ShortestByTime { get; set; }
|
||||||
|
}
|
@ -59,6 +59,12 @@
|
|||||||
"Microsoft.Extensions.Options": "9.0.4"
|
"Microsoft.Extensions.Options": "9.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.5.0, )",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"System.Linq.Dynamic.Core": {
|
"System.Linq.Dynamic.Core": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[1.6.2, )",
|
"requested": "[1.6.2, )",
|
||||||
|
@ -742,6 +742,11 @@
|
|||||||
"Npgsql": "9.0.3"
|
"Npgsql": "9.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.10",
|
"resolved": "2.1.10",
|
||||||
@ -838,6 +843,7 @@
|
|||||||
"MediatR": "[12.4.1, )",
|
"MediatR": "[12.4.1, )",
|
||||||
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
||||||
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
||||||
|
"QuikGraph": "[2.5.0, )",
|
||||||
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23,4 +23,197 @@ public class VehicleEnrollment : EntityBase
|
|||||||
|
|
||||||
|
|
||||||
public ICollection<Ticket> Tickets { get; set; }
|
public ICollection<Ticket> Tickets { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public DateTimeOffset GetDepartureTime(long DepartureRouteAddressId)
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
|
||||||
|
if (DepartureRouteAddressId == firstRouteAddressId)
|
||||||
|
{
|
||||||
|
return DepartureTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var orderedRouteAddressDetails = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order);
|
||||||
|
|
||||||
|
var timeToDeparture = TimeSpan.Zero;
|
||||||
|
foreach (var routeAddressDetail in orderedRouteAddressDetails)
|
||||||
|
{
|
||||||
|
timeToDeparture =
|
||||||
|
timeToDeparture + routeAddressDetail.CurrentAddressStopTime;
|
||||||
|
|
||||||
|
if (routeAddressDetail.Id == DepartureRouteAddressId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeToDeparture =
|
||||||
|
timeToDeparture += routeAddressDetail.TimeToNextAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DepartureTime + timeToDeparture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeOffset GetDepartureTime()
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
|
||||||
|
return GetDepartureTime(firstRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeOffset GetArrivalTime(long ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
var orderedRouteAddressDetails = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order);
|
||||||
|
|
||||||
|
var timeToDeparture = TimeSpan.Zero;
|
||||||
|
foreach (var routeAddressDetail in orderedRouteAddressDetails)
|
||||||
|
{
|
||||||
|
if (routeAddressDetail.Id == ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeToDeparture =
|
||||||
|
timeToDeparture +
|
||||||
|
routeAddressDetail.TimeToNextAddress +
|
||||||
|
routeAddressDetail.CurrentAddressStopTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DepartureTime + timeToDeparture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeOffset GetArrivalTime()
|
||||||
|
{
|
||||||
|
var lastRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).Last().RouteAddressId;
|
||||||
|
|
||||||
|
return GetArrivalTime(lastRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetTravelTime(
|
||||||
|
long DepartureRouteAddressId, long ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
GetArrivalTime(ArrivalRouteAddressId) -
|
||||||
|
GetDepartureTime(DepartureRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetTravelTime()
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
var lastRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).Last().RouteAddressId;
|
||||||
|
|
||||||
|
return GetTravelTime(firstRouteAddressId, lastRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetTimeInStops(
|
||||||
|
long DepartureRouteAddressId, long ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
var orderedRouteAddressDetails = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order);
|
||||||
|
|
||||||
|
var departureRouteAddressDetail = orderedRouteAddressDetails
|
||||||
|
.Single(e => e.Id == DepartureRouteAddressId);
|
||||||
|
|
||||||
|
var timeInStops = TimeSpan.Zero;
|
||||||
|
foreach (var routeAddressDetail in orderedRouteAddressDetails)
|
||||||
|
{
|
||||||
|
if (routeAddressDetail.RouteAddress.Order <=
|
||||||
|
departureRouteAddressDetail.RouteAddress.Order)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeAddressDetail.Id == ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeInStops += routeAddressDetail.CurrentAddressStopTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeInStops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetTimeInStops()
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
var lastRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).Last().RouteAddressId;
|
||||||
|
|
||||||
|
return GetTimeInStops(firstRouteAddressId, lastRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetNumberOfStops(
|
||||||
|
long DepartureRouteAddressId, long ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
|
.SkipWhile(e => e.Id != DepartureRouteAddressId)
|
||||||
|
.TakeWhile(e => e.Id != ArrivalRouteAddressId)
|
||||||
|
.Count() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetNumberOfStops()
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
var lastRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).Last().RouteAddressId;
|
||||||
|
|
||||||
|
return GetTimeInStops(firstRouteAddressId, lastRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetTimeMoving(
|
||||||
|
long DepartureRouteAddressId, long ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
|
.SkipWhile(e => e.Id != DepartureRouteAddressId)
|
||||||
|
.TakeWhile(e => e.Id != ArrivalRouteAddressId)
|
||||||
|
.Aggregate(TimeSpan.Zero,
|
||||||
|
(sum, next) => sum += next.TimeToNextAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetTimeMoving()
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
var lastRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).Last().RouteAddressId;
|
||||||
|
|
||||||
|
return GetTimeMoving(firstRouteAddressId, lastRouteAddressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal GetCost(
|
||||||
|
long DepartureRouteAddressId, long ArrivalRouteAddressId)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
|
.SkipWhile(e => e.Id != DepartureRouteAddressId)
|
||||||
|
.TakeWhile(e => e.Id != ArrivalRouteAddressId)
|
||||||
|
.Aggregate((decimal)0,
|
||||||
|
(sum, next) => sum += next.CostToNextAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal GetCost()
|
||||||
|
{
|
||||||
|
var firstRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).First().RouteAddressId;
|
||||||
|
var lastRouteAddressId = RouteAddressDetails
|
||||||
|
.OrderBy(e => e.RouteAddress.Order).Last().RouteAddressId;
|
||||||
|
|
||||||
|
return GetCost(firstRouteAddressId, lastRouteAddressId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
54
src/HttpApi/Controllers/VehicleEnrollmentSearchController.cs
Normal file
54
src/HttpApi/Controllers/VehicleEnrollmentSearchController.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch;
|
||||||
|
using cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch
|
||||||
|
.Queries.SearchShortest;
|
||||||
|
using cuqmbr.TravelGuide.Application.VehicleEnrollmentSearch.ViewModels;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||||
|
|
||||||
|
[Route("vehicleEnrollmentSearch")]
|
||||||
|
public class VehicleEnrollmentSearchController : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[SwaggerOperation("Search vehicle enrollments with transfers")]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status200OK, "Search successful",
|
||||||
|
typeof(VehicleEnrollmentSearchDto))]
|
||||||
|
[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, "No enrollments found",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
public async Task<ActionResult<VehicleEnrollmentSearchDto>> Add(
|
||||||
|
[FromQuery] SearchShortestViewModel viewModel,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return StatusCode(
|
||||||
|
StatusCodes.Status201Created,
|
||||||
|
await Mediator.Send(
|
||||||
|
new SearchShortestQuery()
|
||||||
|
{
|
||||||
|
DepartureAddressGuid = viewModel.DepartureAddressUuid,
|
||||||
|
ArrivalAddressGuid = viewModel.ArrivalAddressUuid,
|
||||||
|
DepartureDate = viewModel.DepartureDate,
|
||||||
|
VehicleTypes = viewModel.VehicleTypes
|
||||||
|
.Select(e => VehicleType.FromName(e)).ToHashSet(),
|
||||||
|
ShortestByCost = viewModel.ShortestByCost,
|
||||||
|
ShortestByTime = viewModel.ShortestByTime
|
||||||
|
},
|
||||||
|
cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
@ -897,6 +897,11 @@
|
|||||||
"Npgsql": "9.0.3"
|
"Npgsql": "9.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.10",
|
"resolved": "2.1.10",
|
||||||
@ -1079,6 +1084,7 @@
|
|||||||
"MediatR": "[12.4.1, )",
|
"MediatR": "[12.4.1, )",
|
||||||
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
||||||
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
||||||
|
"QuikGraph": "[2.5.0, )",
|
||||||
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -528,6 +528,11 @@
|
|||||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"System.Buffers": {
|
"System.Buffers": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.6.0",
|
"resolved": "4.6.0",
|
||||||
@ -589,6 +594,7 @@
|
|||||||
"MediatR": "[12.4.1, )",
|
"MediatR": "[12.4.1, )",
|
||||||
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
||||||
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
||||||
|
"QuikGraph": "[2.5.0, )",
|
||||||
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -41,7 +41,7 @@ public sealed class ExchangeApiCurrencyConverterService :
|
|||||||
public async Task<decimal> ConvertAsync(decimal amount, Currency from,
|
public async Task<decimal> ConvertAsync(decimal amount, Currency from,
|
||||||
Currency to, DateTimeOffset time, CancellationToken cancellationToken)
|
Currency to, DateTimeOffset time, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (from.Equals(to))
|
if (from.Equals(to) || to.Equals(Currency.Default))
|
||||||
{
|
{
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,11 @@
|
|||||||
"resolved": "9.0.4",
|
"resolved": "9.0.4",
|
||||||
"contentHash": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA=="
|
"contentHash": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA=="
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"System.Linq.Dynamic.Core": {
|
"System.Linq.Dynamic.Core": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "1.6.2",
|
"resolved": "1.6.2",
|
||||||
@ -249,6 +254,7 @@
|
|||||||
"MediatR": "[12.4.1, )",
|
"MediatR": "[12.4.1, )",
|
||||||
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
||||||
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
||||||
|
"QuikGraph": "[2.5.0, )",
|
||||||
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -274,6 +274,11 @@
|
|||||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.10",
|
"resolved": "2.1.10",
|
||||||
@ -329,6 +334,7 @@
|
|||||||
"MediatR": "[12.4.1, )",
|
"MediatR": "[12.4.1, )",
|
||||||
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
||||||
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
||||||
|
"QuikGraph": "[2.5.0, )",
|
||||||
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -816,6 +816,11 @@
|
|||||||
"Npgsql": "9.0.3"
|
"Npgsql": "9.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"QuikGraph": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.0",
|
||||||
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
|
},
|
||||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.10",
|
"resolved": "2.1.10",
|
||||||
@ -982,6 +987,7 @@
|
|||||||
"MediatR": "[12.4.1, )",
|
"MediatR": "[12.4.1, )",
|
||||||
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
"MediatR.Behaviors.Authorization": "[12.2.0, )",
|
||||||
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
"Microsoft.Extensions.Logging": "[9.0.4, )",
|
||||||
|
"QuikGraph": "[2.5.0, )",
|
||||||
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
"System.Linq.Dynamic.Core": "[1.6.2, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user