add vehicle enrollment employee management

This commit is contained in:
cuqmbr 2025-05-27 18:10:53 +03:00
parent 57264b384c
commit 4ae17c5a91
Signed by: cuqmbr
GPG Key ID: 0AA446880C766199
28 changed files with 1829 additions and 144 deletions

View File

@ -0,0 +1,7 @@
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Common.Interfaces
.Persistence.Repositories;
public interface VehicleEnrollmentEmployeeRepository :
BaseRepository<VehicleEnrollmentEmployee> { }

View File

@ -36,6 +36,8 @@ public interface UnitOfWork : IDisposable
RouteAddressDetailRepository RouteAddressDetailRepository { get; }
VehicleEnrollmentEmployeeRepository VehicleEnrollmentEmployeeRepository { get; }
int Save();
Task<int> SaveAsync(CancellationToken cancellationToken);

View File

@ -17,4 +17,6 @@ public record AddVehicleEnrollmentCommand : IRequest<VehicleEnrollmentDto>
public Guid RouteGuid { get; set; }
public ICollection<RouteAddressDetailModel> RouteAddressDetails { get; set; }
public ICollection<Guid> EmployeeGuids { get; set; }
}

View File

@ -158,6 +158,20 @@ public class AddVehicleEnrollmentCommandHandler :
}
// Check if employee guids are valid
var employees = (await _unitOfWork.EmployeeRepository
.GetPageAsync(
e => request.EmployeeGuids.Contains(e.Guid),
1, request.EmployeeGuids.Count, cancellationToken))
.Items;
if (employees.Count < request.EmployeeGuids.Count)
{
throw new NotFoundException();
}
// Create entity and add to datastore.
var entity = new VehicleEnrollment()
@ -166,6 +180,8 @@ public class AddVehicleEnrollmentCommandHandler :
Currency = request.Currency,
VehicleId = vehicle.Id,
RouteId = route.Id,
Route = route,
Vehicle = vehicle,
RouteAddressDetails = route.RouteAddresses
.OrderBy(ra => ra.Order)
.Select(ra => new RouteAddressDetail()
@ -181,6 +197,12 @@ public class AddVehicleEnrollmentCommandHandler :
.CurrentAddressStopTime,
RouteAddressId = ra.Id
})
.ToArray(),
VehicleEnrollmentEmployees = request.EmployeeGuids.Select(g =>
new VehicleEnrollmentEmployee()
{
EmployeeId = employees.Single(e => e.Guid == g).Id
})
.ToArray()
};

View File

@ -1,3 +1,4 @@
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using FluentValidation;
@ -17,12 +18,12 @@ public class AddVehicleEnrollmentCommandValidator :
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)));
.GreaterThanOrEqualTo(DateTimeOffset.Now)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
DateTimeOffset.Now.ToOffset(timeZoneService.TimeZone.BaseUtcOffset)));
RuleFor(v => v.Currency)
.Must(c => Currency.Enumerations.ContainsValue(c))
@ -41,16 +42,36 @@ public class AddVehicleEnrollmentCommandValidator :
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"]);
RuleForEach(v => v.RouteAddressDetails).ChildRules(rad =>
{
rad.RuleFor(rad => rad.RouteAddressGuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
rad.RuleFor(rad => rad.TimeToNextAddress)
.GreaterThanOrEqualTo(TimeSpan.Zero)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
TimeSpan.Zero));
rad.RuleFor(rad => rad.CostToNextAddress)
.GreaterThanOrEqualTo(0)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
0));
rad.RuleFor(rad => rad.CurrentAddressStopTime)
.GreaterThanOrEqualTo(TimeSpan.Zero)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
TimeSpan.Zero));
});
RuleFor(v => v.VehicleGuid)
.NotEmpty()
@ -59,5 +80,21 @@ public class AddVehicleEnrollmentCommandValidator :
RuleFor(v => v.RouteGuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
RuleFor(v => v.EmployeeGuids.Count)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1));
RuleFor(v => v.EmployeeGuids)
.IsUnique(g => g)
.WithMessage(localizer["FluentValidation.IsUnique"]);
RuleForEach(v => v.EmployeeGuids)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -15,4 +15,6 @@ public record UpdateVehicleEnrollmentCommand : IRequest<VehicleEnrollmentDto>
public ICollection<RouteAddressDetailModel> RouteAddressDetails { get; set; }
public ICollection<Guid> EmployeeGuids { get; set; }
}

View File

@ -136,8 +136,56 @@ public class UpdateVehicleEnrollmentCommandHandler :
}
// Check if employee guids are valid
var employees = (await _unitOfWork.EmployeeRepository
.GetPageAsync(
e => request.EmployeeGuids.Contains(e.Guid),
1, request.EmployeeGuids.Count, cancellationToken))
.Items;
if (employees.Count < request.EmployeeGuids.Count)
{
throw new NotFoundException();
}
// Get vehicle and hydrate vehicle enrollment
var vehicle = await _unitOfWork.VehicleRepository
.GetOneAsync(e => e.Id == entity.VehicleId, cancellationToken);
entity.Vehicle = vehicle;
// Update entity and add to datastore.
var requestEmployeeGuids = request.EmployeeGuids;
var datastoreEmployeeGuids = (await _unitOfWork
.VehicleEnrollmentEmployeeRepository.GetPageAsync(
e => e.VehicleEnrollmentId == entity.Id,
e => e.Employee,
1, int.MaxValue, cancellationToken))
.Items
.Select(e => e.Employee.Guid);
var commonEmployeeGuids = datastoreEmployeeGuids
.Intersect(requestEmployeeGuids);
var newEmployeeGuids = requestEmployeeGuids
.Except(datastoreEmployeeGuids);
var combinedEmployeeGuids = commonEmployeeGuids
.Union(newEmployeeGuids);
entity.VehicleEnrollmentEmployees = combinedEmployeeGuids
.Select(g =>
new VehicleEnrollmentEmployee()
{
EmployeeId = employees.Single(e => e.Guid == g).Id,
VehicleEnrollmentId = entity.Id
})
.ToList();
entity.DepartureTime = request.DepartureTime;
entity.Currency = request.Currency;

View File

@ -1,3 +1,4 @@
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using FluentValidation;
@ -21,12 +22,12 @@ public class UpdateVehicleEnrollmentCommandValidator :
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)));
.GreaterThanOrEqualTo(DateTimeOffset.Now)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
DateTimeOffset.Now.ToOffset(timeZoneService.TimeZone.BaseUtcOffset)));
RuleFor(v => v.Currency)
.Must(c => Currency.Enumerations.ContainsValue(c))
@ -45,14 +46,51 @@ public class UpdateVehicleEnrollmentCommandValidator :
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"]);
RuleForEach(v => v.RouteAddressDetails).ChildRules(rad =>
{
rad.RuleFor(rad => rad.RouteAddressGuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
rad.RuleFor(rad => rad.TimeToNextAddress)
.GreaterThanOrEqualTo(TimeSpan.Zero)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
TimeSpan.Zero));
rad.RuleFor(rad => rad.CostToNextAddress)
.GreaterThanOrEqualTo(0)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
0));
rad.RuleFor(rad => rad.CurrentAddressStopTime)
.GreaterThanOrEqualTo(TimeSpan.Zero)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
TimeSpan.Zero));
});
RuleFor(v => v.EmployeeGuids.Count)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1));
RuleFor(v => v.EmployeeGuids)
.IsUnique(g => g)
.WithMessage(localizer["FluentValidation.IsUnique"]);
RuleForEach(v => v.EmployeeGuids)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -42,24 +42,44 @@ public class GetVehicleEnrollmentQueryHandler :
throw new NotFoundException();
}
// Hydrate vehicle enrollment with address information
// Hydrate vehicle enrollment with address,
// vehicle, route and employee information.
var routeAddresses = await _unitOfWork.RouteAddressRepository
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);
1, entity.RouteAddressDetails.Count(), cancellationToken))
.Items;
foreach (var rad in entity.RouteAddressDetails)
{
rad.RouteAddress = routeAddresses.Items
rad.RouteAddress = routeAddresses
.First(ra => ra.Id == rad.RouteAddressId);
}
var vehicle = await _unitOfWork.VehicleRepository
.GetOneAsync(e => e.Id == entity.VehicleId, cancellationToken);
var route = await _unitOfWork.RouteRepository
.GetOneAsync(e => e.Id == entity.RouteId, cancellationToken);
var vehicleEnrollmentEmployees =
(await _unitOfWork.VehicleEnrollmentEmployeeRepository
.GetPageAsync(
e => e.VehicleEnrollmentId == entity.Id,
e => e.Employee,
1, int.MaxValue, cancellationToken))
.Items;
entity.Vehicle = vehicle;
entity.Route = route;
entity.VehicleEnrollmentEmployees =
vehicleEnrollmentEmployees.ToArray();
// Convert currency

View File

@ -49,4 +49,6 @@ public record GetVehicleEnrollmentsPageQuery :
public decimal? CostLessThanOrEqual { get; set; }
public Currency? Currency { get; set; }
public ICollection<Guid>? EmployeeGuids { get; set; }
}

View File

@ -35,120 +35,162 @@ public class GetVehicleEnrollmentsPageQueryHandler :
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);
var pagedList = 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) &&
(request.EmployeeGuids != null
? e.VehicleEnrollmentEmployees.Any(e => request.EmployeeGuids.Contains(e.Employee.Guid))
: true),
e => e.RouteAddressDetails,
request.PageNumber, request.PageSize, cancellationToken);
var vehicleEnrollments = pagedList.Items;
var vehicleIds = vehicleEnrollments.Select(e => e.VehicleId);
var vehicles = (await _unitOfWork.VehicleRepository
.GetPageAsync(
e => vehicleIds.Contains(e.Id),
1, vehicleIds.Count(), cancellationToken))
.Items;
var routeIds = vehicleEnrollments.Select(e => e.RouteId);
var routes = (await _unitOfWork.RouteRepository
.GetPageAsync(
e => routeIds.Contains(e.Id),
1, routeIds.Count(), cancellationToken))
.Items;
// Hydrate vehicle enrollment with address information
// Hydrate vehicle enrollment with address,
// vehicle, route and employee information.
var routeAddressIds = paginatedList.Items
var routeAddressIds = vehicleEnrollments
.SelectMany(ve => ve.RouteAddressDetails)
.Select(rad => rad.RouteAddressId);
var routeAddresses = await _unitOfWork.RouteAddressRepository
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);
1, routeAddressIds.Count(), cancellationToken))
.Items;
foreach (var vehicleEnrollment in paginatedList.Items)
var vehicleEnrollmentIds = vehicleEnrollments
.Select(ve => ve.Id);
var vehicleEnrollmentEmployees =
(await _unitOfWork.VehicleEnrollmentEmployeeRepository
.GetPageAsync(
e => vehicleEnrollmentIds.Contains(e.VehicleEnrollmentId),
e => e.Employee,
1, int.MaxValue, cancellationToken))
.Items;
foreach (var vehicleEnrollment in vehicleEnrollments)
{
foreach (var routeAddressDetail in
vehicleEnrollment.RouteAddressDetails)
{
routeAddressDetail.RouteAddress = routeAddresses.Items
routeAddressDetail.RouteAddress = routeAddresses
.First(ra => ra.Id == routeAddressDetail.RouteAddressId);
}
vehicleEnrollment.Route = routes
.Single(e => e.Id == vehicleEnrollment.RouteId);
vehicleEnrollment.Vehicle = vehicles
.Single(e => e.Id == vehicleEnrollment.VehicleId);
vehicleEnrollment.VehicleEnrollmentEmployees =
vehicleEnrollmentEmployees
.Where(vee => vee.VehicleEnrollmentId == vehicleEnrollment.Id)
.ToArray();
}
@ -158,7 +200,7 @@ public class GetVehicleEnrollmentsPageQueryHandler :
if (!_sessionCurrencyService.Currency.Equals(Currency.Default))
{
foreach (var ve in paginatedList.Items)
foreach (var ve in vehicleEnrollments)
{
foreach (var rad in ve.RouteAddressDetails)
{
@ -171,17 +213,18 @@ public class GetVehicleEnrollmentsPageQueryHandler :
}
var mappedItems = _mapper
.ProjectTo<VehicleEnrollmentDto>(paginatedList.Items.AsQueryable());
var vehicleEnrollmentsDto = _mapper
.Map<List<VehicleEnrollmentDto>>(vehicleEnrollments)
.AsQueryable();
mappedItems = QueryableExtension<VehicleEnrollmentDto>
.ApplySort(mappedItems, request.Sort);
vehicleEnrollmentsDto = QueryableExtension<VehicleEnrollmentDto>
.ApplySort(vehicleEnrollmentsDto, request.Sort);
_unitOfWork.Dispose();
return new PaginatedList<VehicleEnrollmentDto>(
mappedItems.ToList(),
paginatedList.TotalCount, request.PageNumber,
vehicleEnrollmentsDto.ToList(),
pagedList.TotalCount, request.PageNumber,
request.PageSize);
}
}

View File

@ -33,8 +33,17 @@ public sealed class VehicleEnrollmentDto : IMapFrom<VehicleEnrollment>
public string Currency { get; set; }
public Guid VehicleUuid { get; set; }
public VehicleEnrollmentVehicleDto Vehicle { get; set; }
public Guid RouteUuid { get; set; }
public ICollection<RouteAddressDetailDto> RouteAddressDetails { get; set; }
// TODO: Add collection of employee dto objects
public ICollection<String> EmployeeUuids { get; set; }
public void Mapping(MappingProfile profile)
{
profile.CreateMap<VehicleEnrollment, VehicleEnrollmentDto>()
@ -45,6 +54,16 @@ public sealed class VehicleEnrollmentDto : IMapFrom<VehicleEnrollment>
d => d.DepartureTime,
opt => opt
.MapFrom<DateTimeOffsetToLocalResolver, DateTimeOffset>(
s => s.DepartureTime));
s => s.DepartureTime))
.ForMember(
d => d.VehicleUuid,
opt => opt.MapFrom(s => s.Vehicle.Guid))
.ForMember(
d => d.RouteUuid,
opt => opt.MapFrom(s => s.Route.Guid))
.ForMember(
d => d.EmployeeUuids,
opt => opt.MapFrom(s =>
s.VehicleEnrollmentEmployees.Select(e => e.Employee.Guid)));
}
}

View File

@ -0,0 +1,94 @@
using cuqmbr.TravelGuide.Application.Common.Mappings;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.VehicleEnrollments;
public sealed class VehicleEnrollmentVehicleDto : IMapFrom<Vehicle>
{
public string Uuid { get; set; }
public string Type { get; set; }
public string Number { get; set; }
public string Model { get; set; }
public short Capacity { get; set; }
public void Mapping(MappingProfile profile)
{
profile.CreateMap<Vehicle, VehicleEnrollmentVehicleDto>()
.ForMember(
d => d.Uuid,
opt => opt.MapFrom(s => s.Guid))
.ForMember(
d => d.Type,
opt => opt.MapFrom(s => s.VehicleType.Name))
.ForMember(
d => d.Number,
opt => opt.MapFrom(
(s, d) =>
{
if (s is Bus)
{
return ((Bus)s).Number;
}
else if (s is Aircraft)
{
return ((Aircraft)s).Number;
}
else if (s is Train)
{
return ((Train)s).Number;
}
else
{
throw new NotImplementedException();
}
}))
.ForMember(
d => d.Model,
opt => opt.MapFrom(
(s, d) =>
{
if (s is Bus)
{
return ((Bus)s).Model;
}
else if (s is Aircraft)
{
return ((Aircraft)s).Model;
}
else if (s is Train)
{
return ((Train)s).Model;
}
else
{
throw new NotImplementedException();
}
}))
.ForMember(
d => d.Capacity,
opt => opt.MapFrom(
(s, d) =>
{
if (s is Bus)
{
return ((Bus)s).Capacity;
}
else if (s is Aircraft)
{
return ((Aircraft)s).Capacity;
}
else if (s is Train)
{
return ((Train)s).Capacity;
}
else
{
throw new NotImplementedException();
}
}));
}
}

View File

@ -12,4 +12,6 @@ public sealed class AddVehicleEnrollmentViewModel
public Guid RouteUuid { get; set; }
public ICollection<RouteAddressDetailViewModel> RouteAddressDetails { get; set; }
public ICollection<Guid> EmployeeUuids { get; set; }
}

View File

@ -43,4 +43,6 @@ public sealed class GetVehicleEnrollmentsPageFilterViewModel
public decimal? CostLessThanOrEqual { get; set; }
public string? Currency { get; set; }
public ICollection<Guid>? EmployeeUuids { get; set; }
}

View File

@ -8,4 +8,6 @@ public sealed class UpdateVehicleEnrollmentViewModel
public ICollection<RouteAddressDetailViewModel> RouteAddressDetails { get; set; }
public ICollection<Guid> EmployeeUuids { get; set; }
}

View File

@ -20,4 +20,6 @@ public sealed class Employee : EntityBase
public Company Company { get; set; }
public ICollection<EmployeeDocument> Documents { get; set; }
public ICollection<VehicleEnrollmentEmployee> VehicleEnrollmentEmployees { get; set; }
}

View File

@ -21,9 +21,10 @@ public class VehicleEnrollment : EntityBase
public ICollection<RouteAddressDetail> RouteAddressDetails { get; set; }
public ICollection<Ticket> Tickets { get; set; }
public ICollection<VehicleEnrollmentEmployee> VehicleEnrollmentEmployees { get; set; }
public DateTimeOffset GetDepartureTime(long DepartureRouteAddressId)
{

View File

@ -0,0 +1,13 @@
namespace cuqmbr.TravelGuide.Domain.Entities;
public class VehicleEnrollmentEmployee : EntityBase
{
public long EmployeeId { get; set; }
public Employee Employee { get; set; }
public long VehicleEnrollmentId { get; set; }
public VehicleEnrollment VehicleEnrollment { get; set; }
}

View File

@ -75,7 +75,8 @@ public class VehicleEnrollmentsController : ControllerBase
CurrentAddressStopTime = rad.CurrentAddressStopTime,
RouteAddressGuid = rad.RouteAddressUuid
})
.ToArray()
.ToArray(),
EmployeeGuids = viewModel.EmployeeUuids
},
cancellationToken));
}
@ -141,7 +142,8 @@ public class VehicleEnrollmentsController : ControllerBase
filterQuery.CostGreaterThanOrEqual,
CostLessThanOrEqual =
filterQuery.CostLessThanOrEqual,
Currency = Currency.FromName(filterQuery.Currency)
Currency = Currency.FromName(filterQuery.Currency),
EmployeeGuids = filterQuery.EmployeeUuids
},
cancellationToken);
}
@ -224,7 +226,8 @@ public class VehicleEnrollmentsController : ControllerBase
CurrentAddressStopTime = rad.CurrentAddressStopTime,
RouteAddressGuid = rad.RouteAddressUuid
})
.ToArray()
.ToArray(),
EmployeeGuids = viewModel.EmployeeUuids
},
cancellationToken);
}

View File

@ -32,6 +32,8 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
TicketRepository = new InMemoryTicketRepository(_dbContext);
RouteAddressDetailRepository =
new InMemoryRouteAddressDetailRepository(_dbContext);
VehicleEnrollmentEmployeeRepository =
new InMemoryVehicleEnrollmentEmployeeRepository(_dbContext);
}
public CountryRepository CountryRepository { get; init; }
@ -66,6 +68,8 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
public RouteAddressDetailRepository RouteAddressDetailRepository { get; init; }
public VehicleEnrollmentEmployeeRepository VehicleEnrollmentEmployeeRepository { get; init; }
public int Save()
{
return _dbContext.SaveChanges();

View File

@ -0,0 +1,13 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Persistence.InMemory.Repositories;
public sealed class InMemoryVehicleEnrollmentEmployeeRepository :
InMemoryBaseRepository<VehicleEnrollmentEmployee>,
VehicleEnrollmentEmployeeRepository
{
public InMemoryVehicleEnrollmentEmployeeRepository(
InMemoryDbContext dbContext)
: base(dbContext) { }
}

View File

@ -0,0 +1,70 @@
using cuqmbr.TravelGuide.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
public class VehicleEnrollmentEmployeeConfiguration :
BaseConfiguration<VehicleEnrollmentEmployee>
{
public override void Configure(
EntityTypeBuilder<VehicleEnrollmentEmployee> builder)
{
builder
.ToTable("vehicle_enrollment_employees");
base.Configure(builder);
builder
.Property(vee => vee.EmployeeId)
.HasColumnName("employee_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(vee => vee.Employee)
.WithMany(e => e.VehicleEnrollmentEmployees)
.HasForeignKey(vee => vee.EmployeeId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(vee => vee.EmployeeId)
.Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(vee => vee.EmployeeId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(vee => vee.EmployeeId)
.Metadata.GetColumnName()}");
builder
.Property(vee => vee.VehicleEnrollmentId)
.HasColumnName("vehicle_enrollment_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(vee => vee.VehicleEnrollment)
.WithMany(ve => ve.VehicleEnrollmentEmployees)
.HasForeignKey(vee => vee.VehicleEnrollmentId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(vee => vee.VehicleEnrollmentId)
.Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(vee => vee.VehicleEnrollmentId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(vee => vee.VehicleEnrollmentId)
.Metadata.GetColumnName()}");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Persistence.PostgreSql.Migrations
{
/// <inheritdoc />
public partial class Add_Vehicle_Enrollment_Employee : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateSequence(
name: "vehicle_enrollment_employees_id_sequence",
schema: "application");
migrationBuilder.CreateTable(
name: "vehicle_enrollment_employees",
schema: "application",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.vehicle_enrollment_employees_id_sequence')"),
employee_id = table.Column<long>(type: "bigint", nullable: false),
vehicle_enrollment_id = table.Column<long>(type: "bigint", nullable: false),
uuid = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_vehicle_enrollment_employees", x => x.id);
table.UniqueConstraint("altk_vehicle_enrollment_employees_uuid", x => x.uuid);
table.ForeignKey(
name: "fk_vehicle_enrollment_employees_employee_id",
column: x => x.employee_id,
principalSchema: "application",
principalTable: "employees",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_vehicle_enrollment_employees_vehicle_enrollment_id",
column: x => x.vehicle_enrollment_id,
principalSchema: "application",
principalTable: "vehicle_enrollments",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_vehicle_enrollment_employees_employee_id",
schema: "application",
table: "vehicle_enrollment_employees",
column: "employee_id");
migrationBuilder.CreateIndex(
name: "ix_vehicle_enrollment_employees_vehicle_enrollment_id",
schema: "application",
table: "vehicle_enrollment_employees",
column: "vehicle_enrollment_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "vehicle_enrollment_employees",
schema: "application");
migrationBuilder.DropSequence(
name: "vehicle_enrollment_employees_id_sequence",
schema: "application");
}
}
}

View File

@ -47,6 +47,8 @@ namespace Persistence.PostgreSql.Migrations
modelBuilder.HasSequence("tickets_id_sequence");
modelBuilder.HasSequence("vehicle_enrollment_employees_id_sequence");
modelBuilder.HasSequence("vehicle_enrollments_id_sequence");
modelBuilder.HasSequence("vehicles_id_sequence");
@ -691,6 +693,43 @@ namespace Persistence.PostgreSql.Migrations
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollmentEmployee", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.vehicle_enrollment_employees_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "vehicle_enrollment_employees_id_sequence");
b.Property<long>("EmployeeId")
.HasColumnType("bigint")
.HasColumnName("employee_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<long>("VehicleEnrollmentId")
.HasColumnType("bigint")
.HasColumnName("vehicle_enrollment_id");
b.HasKey("Id")
.HasName("pk_vehicle_enrollment_employees");
b.HasAlternateKey("Guid")
.HasName("altk_vehicle_enrollment_employees_uuid");
b.HasIndex("EmployeeId")
.HasDatabaseName("ix_vehicle_enrollment_employees_employee_id");
b.HasIndex("VehicleEnrollmentId")
.HasDatabaseName("ix_vehicle_enrollment_employees_vehicle_enrollment_id");
b.ToTable("vehicle_enrollment_employees", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Aircraft", b =>
{
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
@ -950,6 +989,27 @@ namespace Persistence.PostgreSql.Migrations
b.Navigation("Vehicle");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollmentEmployee", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Employee", "Employee")
.WithMany("VehicleEnrollmentEmployees")
.HasForeignKey("EmployeeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_vehicle_enrollment_employees_employee_id");
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment")
.WithMany("VehicleEnrollmentEmployees")
.HasForeignKey("VehicleEnrollmentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_vehicle_enrollment_employees_vehicle_enrollment_id");
b.Navigation("Employee");
b.Navigation("VehicleEnrollment");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
{
b.Navigation("AddressRoutes");
@ -975,6 +1035,8 @@ namespace Persistence.PostgreSql.Migrations
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.Navigation("Documents");
b.Navigation("VehicleEnrollmentEmployees");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
@ -1009,6 +1071,8 @@ namespace Persistence.PostgreSql.Migrations
b.Navigation("RouteAddressDetails");
b.Navigation("Tickets");
b.Navigation("VehicleEnrollmentEmployees");
});
#pragma warning restore 612, 618
}

View File

@ -32,6 +32,8 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
TicketRepository = new PostgreSqlTicketRepository(_dbContext);
RouteAddressDetailRepository =
new PostgreSqlRouteAddressDetailRepository(_dbContext);
VehicleEnrollmentEmployeeRepository =
new PostgreSqlVehicleEnrollmentEmployeeRepository(_dbContext);
}
public CountryRepository CountryRepository { get; init; }
@ -66,6 +68,8 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
public RouteAddressDetailRepository RouteAddressDetailRepository { get; init; }
public VehicleEnrollmentEmployeeRepository VehicleEnrollmentEmployeeRepository { get; init; }
public int Save()
{
return _dbContext.SaveChanges();

View File

@ -0,0 +1,13 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Repositories;
public sealed class PostgreSqlVehicleEnrollmentEmployeeRepository :
PostgreSqlBaseRepository<VehicleEnrollmentEmployee>,
VehicleEnrollmentEmployeeRepository
{
public PostgreSqlVehicleEnrollmentEmployeeRepository(
PostgreSqlDbContext dbContext)
: base(dbContext) { }
}