public async Task CreateBookingAsync(
CreateBookingCommand request,
CancellationToken cancellationToken)
{
// Get current logged-in employee
var employeeId = _currentUserService.UserId;
// Rule 1: Purpose is required
if (string.IsNullOrWhiteSpace(request.Purpose))
{
throw new BadRequestException(
"Purpose is required.");
}
// Rule 2: Travel date cannot be in the past
if (request.TravelDate < DateTime.UtcNow)
{
throw new BadRequestException(
"Travel date cannot be in the past.");
}
// Rule 3: Return date cannot be before travel date
if (request.ReturnDate < request.TravelDate)
{
throw new BadRequestException(
"Return date cannot be before travel date.");
}
// Get vehicle
var vehicle = await _vehicleRepository.GetByIdAsync(
request.VehicleId,
cancellationToken);
if (vehicle == null)
{
throw new NotFoundException(
"Vehicle not found.");
}
// Rule 4: Vehicle must be active
if (!vehicle.IsActive)
{
throw new BadRequestException(
"This vehicle is not active.");
}
// Rule 5: Check whether vehicle already has
// an approved/allocated booking for these dates
var vehicleAlreadyBooked =
await _vehicleBookingRepository
.HasVehicleBookingOverlapAsync(
request.VehicleId,
request.TravelDate,
request.ReturnDate,
cancellationToken);
if (vehicleAlreadyBooked)
{
throw new BadRequestException(
"Vehicle is already booked for the selected dates.");
}
// Rule 6: Check whether employee already has
// an approved/allocated booking for these dates
var employeeAlreadyBooked =
await _vehicleBookingRepository
.HasEmployeeBookingOverlapAsync(
employeeId,
request.TravelDate,
request.ReturnDate,
cancellationToken);
if (employeeAlreadyBooked)
{
throw new BadRequestException(
"You already have a booking for the selected dates.");
}
// Create booking
var booking = new VehicleBooking
{
Id = Guid.NewGuid(),
VehicleId = request.VehicleId,
EmployeeId = employeeId,
BookingStatus = BookingStatus.Pending,
TravelDate = request.TravelDate,
ReturnDate = request.ReturnDate,
Purpose = request.Purpose,
Destination = request.Destination,
CreatedDate = DateTime.UtcNow
};
await _vehicleBookingRepository.AddAsync(
booking,
cancellationToken);
// Create initial status history
var statusHistory = new BookingStatusHistory
{
Id = Guid.NewGuid(),
VehicleBookingId = booking.Id,
Status = BookingStatus.Pending,
UpdatedDate = DateTime.UtcNow
};
await _bookingStatusHistoryRepository.AddAsync(
statusHistory,
cancellationToken);
return new CreateBookingCommandResponse
{
BookingId = booking.Id,
VehicleId = booking.VehicleId,
BookingStatus = booking.BookingStatus,
TravelDate = booking.TravelDate,
ReturnDate = booking.ReturnDate,
Purpose = booking.Purpose,
Destination = booking.Destination,
Message = "Booking created successfully."
};
}
public async Task HasVehicleBookingOverlapAsync(
Guid vehicleId,
DateTime travelDate,
DateTime returnDate,
CancellationToken cancellationToken)
{
return await _context.VehicleBookings.AnyAsync(
x =>
x.VehicleId == vehicleId
&&
(x.BookingStatus == BookingStatus.Approved
|| x.BookingStatus == BookingStatus.Allocated)
&&
x.TravelDate <= returnDate
&&
x.ReturnDate >= travelDate,
cancellationToken);
}
public async Task AllocateVehicleAsync(
AllocateVehicleCommand request,
CancellationToken cancellationToken)
{
// Get booking
var booking = await _vehicleBookingRepository.GetByIdAsync(
request.BookingId,
cancellationToken);
if (booking == null)
{
throw new NotFoundException(
"Booking not found.");
}
// Business Rule:
// Vehicle can be allocated only to a Pending booking.
if (booking.BookingStatus != BookingStatus.Pending)
{
throw new BadRequestException(
"Vehicle can be allocated only to a pending booking.");
}
// Get vehicle
var vehicle = await _vehicleRepository.GetByIdAsync(
request.VehicleId,
cancellationToken);
if (vehicle == null)
{
throw new NotFoundException(
"Vehicle not found.");
}
// Business Rule:
// Vehicle must be active.
if (!vehicle.IsActive)
{
throw new BadRequestException(
"This vehicle is not active.");
}
// Business Rule:
// Vehicle cannot be allocated if it already
// has an overlapping approved/allocated booking.
var vehicleAlreadyBooked =
await _vehicleBookingRepository
.HasVehicleActiveBookingOverlapAsync(
request.VehicleId,
booking.TravelDate,
booking.ReturnDate,
booking.Id,
cancellationToken);
if (vehicleAlreadyBooked)
{
throw new BadRequestException(
"Vehicle is already allocated for the selected dates.");
}
// Allocate vehicle
booking.VehicleId = request.VehicleId;
booking.BookingStatus = BookingStatus.Allocated;
// Create status history
var statusHistory = new BookingStatusHistory
{
Id = Guid.NewGuid(),
VehicleBookingId = booking.Id,
Status = BookingStatus.Allocated,
UpdatedDate = DateTime.UtcNow
};
// Update booking
await _vehicleBookingRepository.UpdateAsync(
booking,
cancellationToken);
// Add history
await _bookingStatusHistoryRepository.AddAsync(
statusHistory,
cancellationToken);
return new AllocateVehicleCommandResponse
{
BookingId = booking.Id,
VehicleId = booking.VehicleId,
Status = booking.BookingStatus,
Message = "Vehicle allocated successfully."
};
}
public async Task CompleteTripAsync(
CompleteTripCommand request,
CancellationToken cancellationToken)
{
// Get booking
var booking = await _vehicleBookingRepository.GetByIdAsync(
request.BookingId,
cancellationToken);
if (booking == null)
{
throw new NotFoundException(
"Booking not found.");
}
// Business Rule:
// Trip can be completed only after vehicle is allocated.
if (booking.BookingStatus != BookingStatus.Allocated)
{
throw new BadRequestException(
"Trip can be completed only after the vehicle is allocated.");
}
// Change status
booking.BookingStatus = BookingStatus.Completed;
// Create status history
var statusHistory = new BookingStatusHistory
{
Id = Guid.NewGuid(),
VehicleBookingId = booking.Id,
Status = BookingStatus.Completed,
UpdatedDate = DateTime.UtcNow
};
// Update booking
await _vehicleBookingRepository.UpdateAsync(
booking,
cancellationToken);
// Add history
await _bookingStatusHistoryRepository.AddAsync(
statusHistory,
cancellationToken);
return new CompleteTripCommandResponse
{
BookingId = booking.Id,
Status = booking.BookingStatus,
Message = "Trip completed successfully."
};
}
public async Task> ViewMyBookingsAsync(
ViewMyBookingsQuery request,
CancellationToken cancellationToken)
{
// Get current logged-in employee
var employeeId = _currentUserService.UserId;
var bookings = await _vehicleBookingRepository.ViewMyBookingsAsync(
employeeId,
request.PageNumber,
request.PageSize,
request.Search,
request.SortOrder,
request.SortBy,
cancellationToken);
if (bookings == null || !bookings.Any())
{
throw new BadRequestException(
"Bookings not found.");
}
var bookingResponse = new List<ViewMyBookingsQueryResponse>();
foreach (var booking in bookings)
{
bookingResponse.Add(new ViewMyBookingsQueryResponse
{
Id = booking.Id,
VehicleId = booking.VehicleId,
BookingStatus = booking.BookingStatus,
TravelDate = booking.TravelDate,
ReturnDate = booking.ReturnDate,
Purpose = booking.Purpose,
Destination = booking.Destination,
CreatedDate = booking.CreatedDate
});
}
return bookingResponse;
}
public async Task> ViewMyBookingsAsync(
Guid employeeId,
int pageNumber,
int pageSize,
string? search,
string? sortOrder,
string? sortBy,
CancellationToken cancellationToken)
{
IQueryable query =
_context.VehicleBookings
.AsNoTracking()
.Where(x => x.EmployeeId == employeeId);
// Search
if (!string.IsNullOrWhiteSpace(search))
{
search = search.Trim();
query = query.Where(x =>
x.Purpose.Contains(search) ||
x.Destination.Contains(search));
}
// Sorting
query = sortBy?.ToLower() switch
{
"traveldate" => sortOrder?.ToLower() == "desc"
? query.OrderByDescending(x => x.TravelDate)
: query.OrderBy(x => x.TravelDate),
"returndate" => sortOrder?.ToLower() == "desc"
? query.OrderByDescending(x => x.ReturnDate)
: query.OrderBy(x => x.ReturnDate),
"destination" => sortOrder?.ToLower() == "desc"
? query.OrderByDescending(x => x.Destination)
: query.OrderBy(x => x.Destination),
"purpose" => sortOrder?.ToLower() == "desc"
? query.OrderByDescending(x => x.Purpose)
: query.OrderBy(x => x.Purpose),
"status" => sortOrder?.ToLower() == "desc"
? query.OrderByDescending(x => x.BookingStatus)
: query.OrderBy(x => x.BookingStatus),
"createddate" => sortOrder?.ToLower() == "desc"
? query.OrderByDescending(x => x.CreatedDate)
: query.OrderBy(x => x.CreatedDate),
_ => query.OrderByDescending(x => x.CreatedDate)
};
// Pagination
pageNumber = pageNumber < 1 ? 1 : pageNumber;
pageSize = pageSize < 1
? 10
: Math.Min(pageSize, 100);
query = query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
return await query.ToListAsync(cancellationToken);
}
Top comments (0)