use leptos::*; use validator::Validate; use crate::backend::data::{ApiResponse, CrReservation, Reservation, PublicFormData}; use crate::components::data_form::ForValidation; use cfg_if::cfg_if; use chrono::{NaiveDate, NaiveTime}; cfg_if! { if #[cfg(feature = "ssr")] { use sqlx::{Postgres, Transaction}; use sqlx::query_as; use sqlx::Error; use uuid::Uuid; use std::ops::DerefMut; use std::str::FromStr; use crate::backend::data::ReservationSum; use crate::backend::get_pool; async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result { 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, 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?) } } }} #[server] pub async fn get_public_form_data(day: NaiveDate) -> Result>, 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::>())) } pub fn is_reserved(reservations: &Vec, 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, 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::>(); map.entry(i32::from_str(slot_str.get(1).unwrap_or(&"")).unwrap_or(0)) .and_modify(|slot: &mut Vec>| 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 } }