381 lines
15 KiB
C#
381 lines
15 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Server.Data;
|
|
using Server.Models;
|
|
using SharedModels.DataTransferObjects;
|
|
using SharedModels.DataTransferObjects.Services;
|
|
using SharedModels.Responses;
|
|
|
|
namespace Server.Services;
|
|
|
|
public class VehicleEnrollmentSearchService
|
|
{
|
|
private readonly ApplicationDbContext _dbContext;
|
|
|
|
public VehicleEnrollmentSearchService(ApplicationDbContext dbContext)
|
|
{
|
|
_dbContext = dbContext;
|
|
}
|
|
|
|
public async Task<(bool isSucceed, IActionResult actionResult, IList<EnrollmentGroup> result)>
|
|
GetEnrollments(int fromCityId, int toCityId, DateTime date)
|
|
{
|
|
var dbEnrollments = await _dbContext.VehicleEnrollments
|
|
.Include(ve => ve.Tickets)
|
|
.Include(ve => ve.Vehicle)
|
|
.ThenInclude(v => v.Company)
|
|
.Include(ve => ve.RouteAddressDetails)
|
|
.Include(ve => ve.Route).ThenInclude(r => r.RouteAddresses)
|
|
.ThenInclude(ra => ra.Address).ThenInclude(a => a.City)
|
|
.ThenInclude(c => c.State).ThenInclude(s => s.Country)
|
|
.Where(ve => ve.DepartureDateTimeUtc.Date >= date.Date &&
|
|
ve.DepartureDateTimeUtc.Date <= date.AddDays(3).Date)
|
|
.ToListAsync();
|
|
|
|
var toBeRemovedEnrollmentsIds = new List<int>();
|
|
|
|
// Find routes without transfers
|
|
|
|
var directEnrollments = new List<VehicleEnrollment>();
|
|
|
|
foreach (var e in dbEnrollments)
|
|
{
|
|
if (e.Route.RouteAddresses.Count(ra => ra.Address.CityId == fromCityId) == 0 ||
|
|
e.Route.RouteAddresses.Count(ra => ra.Address.CityId == toCityId) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var fromOrder = e.Route.RouteAddresses.First(ra => ra.Address.CityId == fromCityId).Order;
|
|
var toOrder = e.Route.RouteAddresses.First(ra => ra.Address.CityId == toCityId).Order;
|
|
|
|
if (fromOrder < toOrder)
|
|
{
|
|
directEnrollments.Add(e);
|
|
toBeRemovedEnrollmentsIds.Add(e.Id);
|
|
}
|
|
}
|
|
|
|
dbEnrollments.RemoveAll(e => toBeRemovedEnrollmentsIds.Any(id => id == e.Id));
|
|
toBeRemovedEnrollmentsIds.Clear();
|
|
|
|
foreach (var de in directEnrollments)
|
|
{
|
|
var routeAddresses = de.Route.RouteAddresses;
|
|
|
|
var fromOrder = routeAddresses.First(ra => ra.Address.CityId == fromCityId).Order;
|
|
var toOrder = routeAddresses.First(ra => ra.Address.CityId == toCityId).Order;
|
|
|
|
directEnrollments[directEnrollments.IndexOf(de)].Route.RouteAddresses = routeAddresses
|
|
.OrderBy(ra => ra.Order)
|
|
.SkipWhile(ra => ra.Order < fromOrder)
|
|
.TakeWhile(ra => ra.Order <= toOrder)
|
|
.ToList();
|
|
|
|
directEnrollments[directEnrollments.IndexOf(de)]
|
|
.DepartureDateTimeUtc = GetDepartureTime(de);
|
|
}
|
|
|
|
// Find routes with one transfer
|
|
|
|
// Find enrollments with departure city
|
|
|
|
var enrollmentsWithFrom = new List<VehicleEnrollment>();
|
|
|
|
foreach (var e in dbEnrollments)
|
|
{
|
|
if (e.Route.RouteAddresses.Count(ra => ra.Address.CityId == fromCityId) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (e.Route.RouteAddresses.Any(ra => ra.Address.CityId == fromCityId))
|
|
{
|
|
enrollmentsWithFrom.Add(e);
|
|
toBeRemovedEnrollmentsIds.Add(e.Id);
|
|
}
|
|
}
|
|
|
|
// dbEnrollments.RemoveAll(e => ToBeRemovedEnrollmentsIds.Any(id => id == e.Id));
|
|
toBeRemovedEnrollmentsIds.Clear();
|
|
|
|
foreach (var ef in enrollmentsWithFrom)
|
|
{
|
|
var routeAddresses = ef.Route.RouteAddresses;
|
|
|
|
var fromOrder = routeAddresses.First(ra => ra.Address.CityId == fromCityId).Order;
|
|
|
|
enrollmentsWithFrom[enrollmentsWithFrom.IndexOf(ef)].Route.RouteAddresses = routeAddresses
|
|
.OrderBy(ra => ra.Order)
|
|
.SkipWhile(ra => ra.Order < fromOrder).ToList();
|
|
}
|
|
|
|
// Find enrollments with arrival city
|
|
|
|
var enrollmentsWithTo = new List<VehicleEnrollment>();
|
|
|
|
foreach (var e in dbEnrollments)
|
|
{
|
|
if (e.Route.RouteAddresses.Count(ra => ra.Address.CityId == toCityId) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (e.Route.RouteAddresses.Any(ra => ra.Address.CityId == toCityId))
|
|
{
|
|
enrollmentsWithTo.Add(e);
|
|
toBeRemovedEnrollmentsIds.Add(e.Id);
|
|
}
|
|
}
|
|
|
|
// dbEnrollments.RemoveAll(e => ToBeRemovedEnrollmentsIds.Any(id => id == e.Id));
|
|
toBeRemovedEnrollmentsIds.Clear();
|
|
|
|
foreach (var et in enrollmentsWithTo)
|
|
{
|
|
var routeAddresses = et.Route.RouteAddresses;
|
|
|
|
var toOrder = routeAddresses.First(ra => ra.Address.CityId == toCityId).Order;
|
|
|
|
enrollmentsWithTo[enrollmentsWithTo.IndexOf(et)].Route.RouteAddresses = routeAddresses
|
|
.OrderBy(ra => ra.Order)
|
|
.TakeWhile(ra => ra.Order <= toOrder).ToList();
|
|
}
|
|
|
|
// Find intersection of
|
|
// enrollments with only departure city and
|
|
// enrollments with only arrival city
|
|
|
|
var oneTransferPath = new List<List<VehicleEnrollment>>();
|
|
|
|
foreach (var ef in enrollmentsWithFrom)
|
|
{
|
|
foreach (var et in enrollmentsWithTo)
|
|
{
|
|
var efRouteAddresses = ef.Route.RouteAddresses;
|
|
var etRouteAddresses = et.Route.RouteAddresses;
|
|
|
|
var intersectionAddressId = efRouteAddresses.IntersectBy(
|
|
etRouteAddresses.Select(x => x.AddressId),
|
|
x => x.AddressId).FirstOrDefault()?.AddressId;
|
|
|
|
if (intersectionAddressId == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var toOrder = efRouteAddresses.First(ra => ra.AddressId == intersectionAddressId).Order;
|
|
var fromOrder = etRouteAddresses.First(ra => ra.AddressId == intersectionAddressId).Order;
|
|
|
|
enrollmentsWithFrom[enrollmentsWithFrom.IndexOf(ef)].Route.RouteAddresses =
|
|
efRouteAddresses.OrderBy(ra => ra.Order)
|
|
.TakeWhile(ra => ra.Order <= toOrder).ToList();
|
|
enrollmentsWithTo[enrollmentsWithTo.IndexOf(et)].Route.RouteAddresses =
|
|
etRouteAddresses.OrderBy(ra => ra.Order)
|
|
.SkipWhile(ra => ra.Order < fromOrder).ToList();
|
|
|
|
var fromArrivalTime = GetArrivalTime(ef);
|
|
var toDepartureTime = GetDepartureTime(et);
|
|
|
|
var doesIntersect = intersectionAddressId != null;
|
|
|
|
if (doesIntersect && (toDepartureTime - fromArrivalTime) >= TimeSpan.FromMinutes(5))
|
|
{
|
|
oneTransferPath.Add(new List<VehicleEnrollment> {ef, et});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Combine enrollments with transfers and enrollments without transfers
|
|
|
|
foreach (var directEnrollment in directEnrollments)
|
|
{
|
|
oneTransferPath.Add(new List<VehicleEnrollment> {directEnrollment});
|
|
}
|
|
|
|
// Form an object that will be returned
|
|
|
|
var result = new SearchEnrollmentResponse();
|
|
|
|
foreach (var path in oneTransferPath)
|
|
{
|
|
var enrollmentGroup = new EnrollmentGroup();
|
|
int j = 1;
|
|
foreach (var vehicleEnrollment in path)
|
|
{
|
|
enrollmentGroup.Enrollments.Add(new FlattenedEnrollment
|
|
{
|
|
Id = vehicleEnrollment.Id,
|
|
|
|
DepartureAddressId = vehicleEnrollment.Route.RouteAddresses.First().AddressId,
|
|
DepartureTime = GetDepartureTime(vehicleEnrollment),
|
|
DepartureAddressName = vehicleEnrollment.Route.RouteAddresses.First().Address.Name,
|
|
DepartureCityName = vehicleEnrollment.Route.RouteAddresses.First().Address.City.Name,
|
|
DepartureStateName = vehicleEnrollment.Route.RouteAddresses.First().Address.City.State.Name,
|
|
DepartureCountryName = vehicleEnrollment.Route.RouteAddresses.First().Address.City.State.Country.Name,
|
|
DepartureAddressFullName = vehicleEnrollment.Route.RouteAddresses.First().Address.GetFullName(),
|
|
|
|
ArrivalAddressId = vehicleEnrollment.Route.RouteAddresses.Last().AddressId,
|
|
ArrivalTime = GetArrivalTime(vehicleEnrollment),
|
|
ArrivalAddressName = vehicleEnrollment.Route.RouteAddresses.Last().Address.Name,
|
|
ArrivalCityName = vehicleEnrollment.Route.RouteAddresses.Last().Address.City.Name,
|
|
ArrivalStateName = vehicleEnrollment.Route.RouteAddresses.Last().Address.City.State.Name,
|
|
ArrivalCountryName = vehicleEnrollment.Route.RouteAddresses.Last().Address.City.State.Country.Name,
|
|
ArrivalAddressFullName = vehicleEnrollment.Route.RouteAddresses.Last().Address.GetFullName(),
|
|
|
|
Order = j,
|
|
|
|
VehicleId = vehicleEnrollment.VehicleId,
|
|
VehicleNumber = vehicleEnrollment.Vehicle.Number,
|
|
VehicleType = vehicleEnrollment.Vehicle.Type,
|
|
|
|
CompanyId = vehicleEnrollment.Vehicle.CompanyId,
|
|
CompanyName = vehicleEnrollment.Vehicle.Company.Name
|
|
});
|
|
|
|
j++;
|
|
}
|
|
|
|
enrollmentGroup.Cost = GetTotalCost(path);
|
|
enrollmentGroup.Duration = GetTotalDuration(path);
|
|
|
|
result.EnrollmentGroups.Add(enrollmentGroup);
|
|
}
|
|
|
|
if (result.EnrollmentGroups.Count == 0)
|
|
{
|
|
return (false, new NotFoundResult(), null!);
|
|
}
|
|
|
|
return (true, null!, result.EnrollmentGroups);
|
|
|
|
DateTime GetDepartureTime(VehicleEnrollment enrollment)
|
|
{
|
|
var departureDateTimeUtc = enrollment.DepartureDateTimeUtc;
|
|
|
|
var departureRouteAddressId = enrollment.Route.RouteAddresses.First().Id;
|
|
|
|
foreach (var detail in enrollment.RouteAddressDetails)
|
|
{
|
|
if (detail.RouteAddressId == departureRouteAddressId)
|
|
{
|
|
departureDateTimeUtc += detail.WaitTimeSpan;
|
|
break;
|
|
}
|
|
|
|
departureDateTimeUtc += detail.TimeSpanToNextCity + detail.WaitTimeSpan;
|
|
}
|
|
|
|
return departureDateTimeUtc;
|
|
}
|
|
|
|
DateTime GetArrivalTime(VehicleEnrollment enrollment)
|
|
{
|
|
var arrivalDateTimeUtc = enrollment.DepartureDateTimeUtc;
|
|
|
|
var arrivalRouteAddressId = enrollment.Route.RouteAddresses.Last().Id;
|
|
|
|
foreach (var detail in enrollment.RouteAddressDetails)
|
|
{
|
|
if (detail.RouteAddressId == arrivalRouteAddressId)
|
|
{
|
|
break;
|
|
}
|
|
|
|
arrivalDateTimeUtc += detail.TimeSpanToNextCity + detail.WaitTimeSpan;
|
|
}
|
|
|
|
return arrivalDateTimeUtc;
|
|
}
|
|
|
|
TimeSpan GetTotalDuration(List<VehicleEnrollment> vehicleEnrollments)
|
|
{
|
|
return GetArrivalTime(vehicleEnrollments.Last()) -
|
|
GetDepartureTime(vehicleEnrollments.First());
|
|
}
|
|
|
|
double GetTotalCost(List<VehicleEnrollment> vehicleEnrollments)
|
|
{
|
|
double result = 0;
|
|
|
|
foreach (var enrollment in vehicleEnrollments)
|
|
{
|
|
foreach (var routeAddresses in enrollment.Route.RouteAddresses)
|
|
{
|
|
if (enrollment.Route.RouteAddresses.Last().Id == routeAddresses.Id)
|
|
{
|
|
break;
|
|
}
|
|
|
|
result += routeAddresses.RouteAddressDetails.First(rad =>
|
|
rad.VehicleEnrollmentId == enrollment.Id).CostToNextCity;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public async Task<(bool isSucceed, IActionResult actionResult, IList<AutocompleteCityDto> cities)>
|
|
GetPopularCityNames(string type, string name, int limit)
|
|
{
|
|
var dbCities = await _dbContext.Cities.Include(c => c.State).ThenInclude(s => s.Country)
|
|
.Where(c => EF.Functions.ILike(c.Name, $"%{name}%")).ToArrayAsync();
|
|
var dbTicketGroups = await _dbContext.TicketGroups.Include(tg => tg.Tickets)
|
|
.ThenInclude(t => t.VehicleEnrollment).ThenInclude(ve => ve.Route).ThenInclude(r => r.RouteAddresses)
|
|
.ThenInclude(ra => ra.Address).ThenInclude(a => a.City)
|
|
.Where(tg => tg.PurchaseDateTimeUtc >= DateTime.UtcNow.AddDays(-60))
|
|
.ToArrayAsync();
|
|
|
|
var cityFrequency = dbCities.ToDictionary(key => key, value => 0);
|
|
|
|
foreach (var tg in dbTicketGroups)
|
|
{
|
|
var departureCity = tg.GetDepartureAddress().City;
|
|
var arrivalCity = tg.GetArrivalAddress().City;
|
|
|
|
if ((type == "from" || type == "any") && cityFrequency.ContainsKey(departureCity))
|
|
{
|
|
cityFrequency[departureCity]++;
|
|
}
|
|
|
|
if ((type == "to" || type == "any") && cityFrequency.ContainsKey(arrivalCity))
|
|
{
|
|
cityFrequency[arrivalCity]++;
|
|
}
|
|
}
|
|
|
|
var orderedCityFrequency = cityFrequency.OrderByDescending(kvp => kvp.Value);
|
|
|
|
int order = 1;
|
|
var popularCitiesDto = new List<AutocompleteCityDto>();
|
|
foreach (var city in orderedCityFrequency.Take(limit).Select(kvp => kvp.Key))
|
|
{
|
|
popularCitiesDto.Add(new AutocompleteCityDto
|
|
{
|
|
Id = city.Id,
|
|
Name = city.Name,
|
|
StateName = city.State.Name,
|
|
CountryName = city.State.Country.Name,
|
|
FullName = city.GetFullName(),
|
|
Order = order
|
|
});
|
|
|
|
order++;
|
|
}
|
|
|
|
return (true, null!, popularCitiesDto);
|
|
}
|
|
|
|
public async Task<(bool isSucceed, IActionResult actionResult, IList<AutocompleteCityDto> cities)>
|
|
GetPopularCityNames(string type, int limit)
|
|
{
|
|
return await GetPopularCityNames(type, "", limit);
|
|
}
|
|
|
|
public async Task<(bool isSucceed, IActionResult actionResult, IList<AutocompleteCityDto> cities)>
|
|
GetPopularCityNames(int limit)
|
|
{
|
|
return await GetPopularCityNames("any", "", limit);
|
|
}
|
|
} |