use serde::Serialize;
use super::{Db, Ts};
#[derive(Debug, Clone, Serialize, sqlx::FromRow)]
pub struct Reservation {
pub id: i32,
pub station_id: i64,
pub ocpp_version: String,
pub evse_number: Option<i32>,
pub connector_type: Option<String>,
pub id_token: String,
pub id_token_type: Option<String>,
pub group_id_token: Option<String>,
pub expires_at: Ts,
pub status: String,
pub transaction_id: Option<i64>,
pub created_at: Ts,
pub updated_at: Ts,
}
const COLS: &str = "id, station_id, ocpp_version, evse_number, connector_type, id_token, id_token_type, group_id_token, expires_at, status, \
transaction_id, created_at, updated_at";
pub struct NewReservation<'a> {
pub ocpp_version: &'a str,
pub evse_number: Option<i32>,
pub connector_type: Option<&'a str>,
pub id_token: &'a str,
pub id_token_type: Option<&'a str>,
pub group_id_token: Option<&'a str>,
pub expires_at: Ts,
}
pub async fn insert(db: &Db, station_id: i64, r: &NewReservation<'_>) -> sqlx::Result<Reservation> {
sqlx::query_as(&format!(
"INSERT INTO reservations (station_id, ocpp_version, evse_number, connector_type, id_token, id_token_type, group_id_token, expires_at, status) \
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'Requested') RETURNING {COLS}"
))
.bind(station_id)
.bind(r.ocpp_version)
.bind(r.evse_number)
.bind(r.connector_type)
.bind(r.id_token)
.bind(r.id_token_type)
.bind(r.group_id_token)
.bind(r.expires_at)
.fetch_one(db)
.await
}
pub async fn get(db: &Db, station_id: i64, id: i32) -> sqlx::Result<Option<Reservation>> {
sqlx::query_as(&format!(
"SELECT {COLS} FROM reservations WHERE station_id = $1 AND id = $2"
))
.bind(station_id)
.bind(id)
.fetch_optional(db)
.await
}
pub async fn list(
db: &Db,
station_id: i64,
active_only: bool,
limit: i64,
) -> sqlx::Result<Vec<Reservation>> {
sqlx::query_as(&format!(
"SELECT {COLS} FROM reservations WHERE station_id = $1 AND (NOT $2 OR (status = 'Accepted' AND expires_at > now())) ORDER BY id DESC LIMIT $3"
))
.bind(station_id)
.bind(active_only)
.bind(limit)
.fetch_all(db)
.await
}
pub async fn set_status(
db: &Db,
station_id: i64,
id: i32,
status: &str,
from: &[&str],
) -> sqlx::Result<bool> {
let from: Vec<String> = from.iter().map(|s| (*s).to_owned()).collect();
Ok(sqlx::query("UPDATE reservations SET status = $3, updated_at = now() WHERE station_id = $1 AND id = $2 AND status = ANY($4)")
.bind(station_id)
.bind(id)
.bind(status)
.bind(&from)
.execute(db)
.await?
.rows_affected()
> 0)
}
pub async fn mark_used(
db: &Db,
station_id: i64,
id: i32,
transaction_id: i64,
) -> sqlx::Result<bool> {
let mut tx = db.begin().await?;
let updated = sqlx::query(
"UPDATE reservations SET status = 'Used', transaction_id = $3, updated_at = now() WHERE station_id = $1 AND id = $2 AND status IN ('Requested', 'Accepted', 'Expired')",
)
.bind(station_id)
.bind(id)
.bind(transaction_id)
.execute(&mut *tx)
.await?
.rows_affected()
> 0;
if updated {
sqlx::query("UPDATE transactions SET reservation_db_id = $2 WHERE id = $1")
.bind(transaction_id)
.bind(id)
.execute(&mut *tx)
.await?;
}
tx.commit().await?;
Ok(updated)
}
pub async fn remove_for_connector(db: &Db, station_id: i64, connector: i64) -> sqlx::Result<u64> {
Ok(sqlx::query(
"UPDATE reservations SET status = 'Removed', updated_at = now() WHERE station_id = $1 AND status = 'Accepted' \
AND ($2 = 0 OR evse_number = $2)",
)
.bind(station_id)
.bind(connector.clamp(0, i32::MAX as i64) as i32)
.execute(db)
.await?
.rows_affected())
}
pub async fn expire_due(db: &Db) -> sqlx::Result<u64> {
Ok(sqlx::query("UPDATE reservations SET status = 'Expired', updated_at = now() WHERE status = 'Accepted' AND expires_at <= now()")
.execute(db)
.await?
.rows_affected())
}