Pierre HUBERT
1a890844ef
All checks were successful
continuous-integration/drone/push Build is passing
Add a new module to enable accommodations reservation ![](https://gitea.communiquons.org/attachments/de1f5b12-0a93-40f8-b29d-97665daa6fd5) Reviewed-on: #188
224 lines
6.8 KiB
Rust
224 lines
6.8 KiB
Rust
use crate::controllers::HttpResult;
|
|
use crate::extractors::accommodation_extractor::FamilyAndAccommodationInPath;
|
|
use crate::extractors::accommodation_reservation_extractor::FamilyAndAccommodationReservationInPath;
|
|
use crate::extractors::family_extractor::FamilyInPath;
|
|
use crate::models::{Accommodation, AccommodationReservationID, NewAccommodationReservation};
|
|
use crate::services::accommodations_reservations_service;
|
|
use crate::utils::time_utils::time;
|
|
use actix_web::{web, HttpResponse};
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct UpdateReservationQuery {
|
|
start: usize,
|
|
end: usize,
|
|
}
|
|
|
|
impl UpdateReservationQuery {
|
|
/// Check whether a reservation request is valid or not
|
|
async fn validate(
|
|
&self,
|
|
a: &Accommodation,
|
|
resa_id: Option<AccommodationReservationID>,
|
|
) -> anyhow::Result<Option<&str>> {
|
|
if !a.open_to_reservations {
|
|
return Ok(Some(
|
|
"The accommodation is not open to reservations creation / update!",
|
|
));
|
|
}
|
|
|
|
if (self.start as i64) < (time() as i64 - 3600 * 24 * 30) {
|
|
return Ok(Some("Start time is too far in the past!"));
|
|
}
|
|
|
|
if self.start == self.end {
|
|
return Ok(Some("Start and end time must be different!"));
|
|
}
|
|
|
|
if self.start > self.end {
|
|
return Ok(Some("End time happens before start time!"));
|
|
}
|
|
|
|
let existing = accommodations_reservations_service::get_reservations_for_time_interval(
|
|
a.id(),
|
|
self.start,
|
|
self.end,
|
|
)
|
|
.await?;
|
|
|
|
if existing
|
|
.iter()
|
|
.any(|r| r.validated != Some(false) && resa_id != Some(r.id()))
|
|
{
|
|
return Ok(Some("This reservation is in conflict with another one!"));
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
/// Create a reservation
|
|
pub async fn create_reservation(
|
|
a: FamilyAndAccommodationInPath,
|
|
req: web::Json<UpdateReservationQuery>,
|
|
) -> HttpResult {
|
|
if let Some(err) = req.validate(&a, None).await? {
|
|
return Ok(HttpResponse::BadRequest().json(err));
|
|
}
|
|
|
|
let mut reservation =
|
|
accommodations_reservations_service::create(&NewAccommodationReservation {
|
|
family_id: a.family_id().0,
|
|
accommodation_id: a.id().0,
|
|
user_id: a.membership().user_id().0,
|
|
time_create: time() as i64,
|
|
time_update: time() as i64,
|
|
reservation_start: req.start as i64,
|
|
reservation_end: req.end as i64,
|
|
})
|
|
.await?;
|
|
|
|
// Auto validate reservation if requested
|
|
if !a.need_validation {
|
|
reservation.validated = Some(true);
|
|
|
|
accommodations_reservations_service::update(&mut reservation).await?;
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().json(reservation))
|
|
}
|
|
|
|
/// Get the reservations for a given accommodation
|
|
pub async fn get_accommodation_reservations(a: FamilyAndAccommodationInPath) -> HttpResult {
|
|
Ok(HttpResponse::Ok()
|
|
.json(accommodations_reservations_service::get_all_of_accommodation(a.id()).await?))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct CheckAvailabilityQuery {
|
|
start: usize,
|
|
end: usize,
|
|
}
|
|
|
|
/// Check reservation availability
|
|
pub async fn get_accommodation_reservations_for_interval(
|
|
a: FamilyAndAccommodationInPath,
|
|
req: web::Query<CheckAvailabilityQuery>,
|
|
) -> HttpResult {
|
|
if req.start > req.end {
|
|
return Ok(HttpResponse::BadRequest().json("start should be smaller than end!"));
|
|
}
|
|
|
|
let res = accommodations_reservations_service::get_reservations_for_time_interval(
|
|
a.id(),
|
|
req.start,
|
|
req.end,
|
|
)
|
|
.await?;
|
|
|
|
Ok(HttpResponse::Ok().json(res))
|
|
}
|
|
|
|
/// Get the full list of accommodations reservations for a family
|
|
pub async fn full_list(m: FamilyInPath) -> HttpResult {
|
|
Ok(HttpResponse::Ok()
|
|
.json(accommodations_reservations_service::get_all_of_family(m.family_id()).await?))
|
|
}
|
|
|
|
/// Get a single accommodation reservation
|
|
pub async fn get_single(m: FamilyAndAccommodationReservationInPath) -> HttpResult {
|
|
Ok(HttpResponse::Ok().json(m.to_reservation()))
|
|
}
|
|
|
|
/// Update a reservation
|
|
pub async fn update_single(
|
|
m: FamilyAndAccommodationReservationInPath,
|
|
req: web::Json<UpdateReservationQuery>,
|
|
) -> HttpResult {
|
|
if let Some(err) = req.validate(m.as_accommodation(), Some(m.id())).await? {
|
|
return Ok(HttpResponse::BadRequest().json(err));
|
|
}
|
|
|
|
if m.membership().user_id() != m.user_id() {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json("Only the owner of a reservation can change it!")
|
|
);
|
|
}
|
|
|
|
let need_validation = m.as_accommodation().need_validation;
|
|
|
|
let mut reservation = m.to_reservation();
|
|
reservation.reservation_start = req.start as i64;
|
|
reservation.reservation_end = req.end as i64;
|
|
|
|
if need_validation {
|
|
reservation.validated = None;
|
|
}
|
|
|
|
accommodations_reservations_service::update(&mut reservation).await?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|
|
|
|
/// Delete a reservation
|
|
pub async fn delete(m: FamilyAndAccommodationReservationInPath) -> HttpResult {
|
|
if m.membership().user_id() != m.user_id() {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json("Only the owner of a reservation can delete it!")
|
|
);
|
|
}
|
|
|
|
accommodations_reservations_service::delete(m.to_reservation()).await?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct ValidateQuery {
|
|
validate: bool,
|
|
}
|
|
|
|
/// Validate or reject a reservation
|
|
pub async fn validate_or_reject(
|
|
m: FamilyAndAccommodationReservationInPath,
|
|
q: web::Json<ValidateQuery>,
|
|
) -> HttpResult {
|
|
if !m.membership().is_admin {
|
|
return Ok(
|
|
HttpResponse::BadRequest().json("Only a family admin can validate a reservation!")
|
|
);
|
|
}
|
|
|
|
if m.validated == Some(q.validate) {
|
|
return Ok(
|
|
HttpResponse::AlreadyReported().json("This reservation has already been processed!")
|
|
);
|
|
}
|
|
|
|
// In case of re-validation, check that the time is still available
|
|
if m.validated == Some(false) && q.validate {
|
|
let potential_conflicts =
|
|
accommodations_reservations_service::get_reservations_for_time_interval(
|
|
m.accommodation_id(),
|
|
m.reservation_start as usize,
|
|
m.reservation_end as usize,
|
|
)
|
|
.await?;
|
|
|
|
if potential_conflicts
|
|
.iter()
|
|
.any(|a| a.validated != Some(false))
|
|
{
|
|
return Ok(HttpResponse::Conflict().json(
|
|
"This cannot be accepted as it would create a conflict with another reservation!",
|
|
));
|
|
}
|
|
}
|
|
|
|
// Update reservation validation status
|
|
let mut reservation = m.to_reservation();
|
|
reservation.validated = Some(q.validate);
|
|
accommodations_reservations_service::update(&mut reservation).await?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|