2024-02-01 09:18:00 +01:00
use leptos ::* ;
use validator ::Validate ;
2024-02-05 12:00:38 +01:00
use crate ::backend ::data ::{ ApiResponse , CrReservation , Reservation , PublicFormData , ResSumWithItems };
2024-02-01 09:18:00 +01:00
use crate ::components ::data_form ::ForValidation ;
use cfg_if ::cfg_if ;
use chrono ::{ NaiveDate , NaiveTime };
cfg_if! { if #[cfg(feature = "ssr" )] {
2024-02-05 12:00:38 +01:00
use sqlx ::{ Postgres , Transaction , query };
2024-02-01 09:18:00 +01:00
use sqlx ::query_as ;
use sqlx ::Error ;
use uuid ::Uuid ;
use std ::ops ::DerefMut ;
use std ::str ::FromStr ;
2024-02-05 12:00:38 +01:00
use futures_util ::future ::join_all ;
use crate ::backend ::data ::{ ReservationSum , ReservationState , ResWithProperty , Customer };
2024-02-01 09:18:00 +01:00
use crate ::backend ::get_pool ;
async fn find_sum_by_uuid ( uuid : & Uuid , tx : & mut Transaction < '_ , Postgres > ) -> Result < ReservationSum , Error > {
let reservation = query_as ::< _ , ReservationSum > ( "SELECT * FROM reservation_sum WHERE uuid = $1" )
. bind ( uuid )
. fetch_one ( tx . deref_mut ())
. await ? ;
Ok ( reservation )
}
async fn reservations_for_day ( day : & NaiveDate ) -> Result < Vec < Reservation > , ServerFnError > {
let pool = get_pool (). await ? ;
let reservations = query_as ::< _ , Reservation > ( "SELECT * FROM reservation JOIN reservation_sum on reservation.summary=reservation_sum.id WHERE reservation_sum.date=$1" )
. bind ( day )
. fetch_all ( & pool )
. await ;
if let Err ( e ) = reservations {
if matches! ( e , Error ::RowNotFound ) {
Ok ( vec! [])
} else {
Err ( e . into ())
}
} else {
Ok ( reservations ? )
}
}
2024-02-05 12:00:38 +01:00
async fn reservations_in_range ( from : & NaiveDate , to : & NaiveDate , state : Option < ReservationState > ) -> Result < Vec < ResSumWithItems > , ServerFnError > {
let pool = get_pool (). await ? ;
let sums = if let Some ( s ) = state {
query_as ::< _ , ReservationSum > ( "SELECT * FROM reservation_sum WHERE date >= $1 AND date <= $2 AND state = $3 ORDER BY date" )
. bind ( from )
. bind ( to )
. bind ( s )
. fetch_all ( & pool )
. await
} else {
query_as ::< _ , ReservationSum > ( "SELECT * FROM reservation_sum WHERE date >= $1 AND date <= $2 ORDER BY date" )
. bind ( from )
. bind ( to )
. fetch_all ( & pool )
. await
};
let sums = if let Err ( ref e ) = sums {
if matches! ( e , Error ::RowNotFound ) {
vec! []
} else {
sums ?
}
} else {
sums ?
};
if sums . is_empty () {
return Ok ( vec! [])
}
let res : Result < Vec < ResSumWithItems > , Error > = join_all ( sums . into_iter (). map ( | s | async {
let reservations = query_as ::< _ , ResWithProperty > (
"SELECT r.id, r.from, r.to, r.property, r.summary, p.name, p,description \
FROM reservation as r \
JOIN property as p ON r.property = p.id WHERE r.summary = $1" )
. bind ( s . id ())
. fetch_all ( & pool )
. await ? ;
let customer = query_as ::< _ , Customer > ( "SELECT * FROM customer WHERE id = $1" )
. bind ( s . customer )
. fetch_one ( & pool )
. await ? ;
Ok ( ResSumWithItems {
summary : s ,
customer ,
reservations
})
})). await . into_iter (). collect ();
Ok ( res ? )
}
async fn set_state ( uuid : Uuid , state : ReservationState ) -> Result < (), ServerFnError > {
let pool = get_pool (). await ? ;
query ( "UPDATE reservation_sum SET state = $1 WHERE uuid = $2" )
. bind ( state )
. bind ( uuid )
. execute ( & pool )
. await ? ;
Ok (())
}
2024-02-01 09:18:00 +01:00
}}
#[server]
pub async fn get_public_form_data ( day : NaiveDate ) -> Result < ApiResponse < Vec < PublicFormData >> , ServerFnError > {
use crate ::backend ::opening_hours ::hours_for_day ;
use crate ::backend ::property ::get_props ;
use chrono ::Datelike ;
let hours = hours_for_day ( day . weekday ()). await ? ;
let props = get_props ( Some ( "active = true" . to_string ())). await ? ;
let reservations = reservations_for_day ( & day ). await ? ;
Ok ( ApiResponse ::Data ( props . into_iter (). map ( | p | PublicFormData {
property : p ,
hours : hours . clone (),
reservations : reservations . clone ()
}). collect ::< Vec < _ >> ()))
}
pub fn is_reserved ( reservations : & Vec < Reservation > , time : & NaiveTime , property : i32 ) -> bool {
for r in reservations {
if r . property == property && & r . from <= time && time < & r . to {
return true
}
}
false
}
#[server]
pub async fn create_reservation ( reservation : CrReservation ) -> Result < ApiResponse < NaiveDate > , ServerFnError > {
use crate ::backend ::get_pool ;
use crate ::backend ::customer ::find_customer_by_email ;
use crate ::backend ::customer ::sync_customer_data ;
use crate ::backend ::customer ::create_customer ;
use crate ::backend ::property ::get_prop_by_id ;
use crate ::backend ::data ::{ TmCheck , ReservationState , Reservations };
use std ::collections ::HashMap ;
use crate ::error ::AppError ;
use chrono ::Local ;
use sqlx ::query ;
use rust_decimal ::Decimal ;
let slots = reservation . slots (). iter (). fold ( HashMap ::new (), | mut map , s | {
let slot_str = s . split ( "|" ). collect ::< Vec < _ >> ();
map . entry ( i32 ::from_str ( slot_str . get ( 1 ). unwrap_or ( & "" )). unwrap_or ( 0 ))
. and_modify ( | slot : & mut Vec < Result < TmCheck , AppError >>| slot . push ( TmCheck ::from_str ( slot_str . get ( 0 ). unwrap_or ( & "" ))))
. or_insert ( vec! [ TmCheck ::from_str ( slot_str . get ( 0 ). unwrap_or ( & "" ))]);
map
});
let res_for_day = reservations_for_day ( & reservation . date ()). await ? ;
let mut reservations = Reservations ::new ();
let mut price = Decimal ::from ( 0 );
for sl in slots {
let mut checks = sl . 1. clone ();
checks . sort ();
let property = get_prop_by_id ( sl . 0 ). await ? ;
for c in checks {
reservations . add_slot ( & c . clone () ? , sl . 0 );
price = price + property . price ;
if is_reserved ( & res_for_day , & c ? . from , sl . 0 ) {
return Ok ( ApiResponse ::Error ( "Slot and time already booked" . to_string ()))
}
}
}
let pool = get_pool (). await ? ;
let mut tx = pool . begin (). await ? ;
let customer = if let Some ( c ) = find_customer_by_email ( reservation . email (), & mut tx ). await {
sync_customer_data ( & c , reservation . full_name (), reservation . phone (), & mut tx ). await ?
} else {
create_customer ( reservation . full_name (), reservation . email (), reservation . phone (), & mut tx ). await ?
};
let res_uuid = Uuid ::new_v4 ();
query ( "INSERT INTO reservation_sum(uuid, date, customer, price, state, note, date_create) VALUES($1, $2, $3, $4, $5, $6, $7)" )
. bind ( res_uuid )
. bind ( reservation . date ())
. bind ( customer . id ())
. bind ( price )
. bind ( ReservationState ::New )
. bind ( reservation . note ())
. bind ( Local ::now (). date_naive ())
. execute ( tx . deref_mut ())
. await ? ;
let sum = find_sum_by_uuid ( & res_uuid , & mut tx ). await ? ;
for r in reservations . reservations () {
query ( r #"INSERT INTO reservation("from", "to", property, summary) VALUES($1, $2, $3, $4)"# )
. bind ( r . from )
. bind ( r . to )
. bind ( r . property )
. bind ( sum . id ())
. execute ( tx . deref_mut ())
. await ? ;
}
tx . commit (). await ? ;
Ok ( ApiResponse ::Data ( reservation . date ()))
}
impl ForValidation for CreateReservation {
fn entity ( & self ) -> & dyn Validate {
& self . reservation
}
2024-02-05 12:00:38 +01:00
}
#[server]
pub async fn get_new_reservations () -> Result < ApiResponse < Vec < ResSumWithItems >> , ServerFnError > {
use crate ::perm_check ;
use chrono ::{ Days , Local };
use crate ::backend ::data ::ReservationState ;
perm_check! ( is_logged_in );
Ok ( ApiResponse ::Data ( reservations_in_range ( & Local ::now (). date_naive (),
& Local ::now (). checked_add_days ( Days ::new ( 7 )). unwrap (). date_naive (),
Some ( ReservationState ::New )). await ? ))
}
#[server]
pub async fn get_next_reservations () -> Result < ApiResponse < Vec < ResSumWithItems >> , ServerFnError > {
use crate ::perm_check ;
use chrono ::{ Days , Local };
use crate ::backend ::data ::ReservationState ;
perm_check! ( is_logged_in );
Ok ( ApiResponse ::Data ( reservations_in_range ( & Local ::now (). date_naive (),
& Local ::now (). checked_add_days ( Days ::new ( 7 )). unwrap (). date_naive (),
Some ( ReservationState ::Approved )). await ? ))
}
#[server]
pub async fn approve ( uuid : String ) -> Result < ApiResponse < () > , ServerFnError > {
use crate ::perm_check ;
use crate ::backend ::data ::ReservationState ;
perm_check! ( is_logged_in );
set_state ( Uuid ::parse_str ( & uuid ) ? , ReservationState ::Approved ). await ? ;
Ok ( ApiResponse ::Data (()))
}
#[server]
pub async fn cancel ( uuid : String ) -> Result < ApiResponse < () > , ServerFnError > {
use crate ::perm_check ;
use crate ::backend ::data ::ReservationState ;
perm_check! ( is_logged_in );
set_state ( Uuid ::parse_str ( & uuid ) ? , ReservationState ::Canceled ). await ? ;
Ok ( ApiResponse ::Data (()))
2024-02-01 09:18:00 +01:00
}