cdb/src/transaction.rs
2024-10-07 20:53:58 +02:00

204 lines
6 KiB
Rust

use futures::StreamExt;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::prelude::FromRow;
use crate::{get_itemdb, get_locations, get_pg, routes::ToAPI};
// todo : produced / consumed by flow field?
/// A Transaction of an Item Variant
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Transaction {
/// UUID
pub id: uuid::Uuid,
/// Associated Item
pub item: String,
/// Associated Variant
pub variant: String,
/// Price of obtaining the Item
pub price: f64,
/// Origin of the Item
pub origin: Option<String>,
/// The location of the Item
pub location: Option<String>,
/// Notes on Transaction
pub note: Option<String>,
/// Timestamp of the Transaction
pub created: chrono::DateTime<chrono::Utc>,
/// Destination of the Item or who consumed it
pub destination: Option<String>,
/// Price the Item was exported or consumed at
pub consumed_price: Option<f64>,
/// Timestamp of Consumption
pub consumed_timestamp: Option<chrono::DateTime<chrono::Utc>>,
}
impl Transaction {
pub async fn new(
item: &str,
variant: &str,
price: f64,
origin: Option<&str>,
location: Option<&str>,
note: Option<&str>,
) -> Self {
sqlx::query_as("INSERT INTO transactions (item, variant, price, origin, location, note) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *")
.bind(item)
.bind(variant)
.bind(price)
.bind(origin)
.bind(location)
.bind(note)
.fetch_one(get_pg!()).await.unwrap()
}
pub async fn get(id: &uuid::Uuid) -> Option<Self> {
sqlx::query_as("SELECT * FROM transactions WHERE id = $1")
.bind(id)
.fetch_optional(get_pg!())
.await
.unwrap()
}
/// Consumes the Item with `price` and `destination`
pub async fn consume(self, price: f64, destination: &str) -> Self {
sqlx::query_as(
"UPDATE transactions SET consumed_timestamp = current_timestamp, consumed_price = $1, destination = $2 WHERE id = $3 RETURNING *")
.bind(price)
.bind(destination)
.bind(&self.id)
.fetch_one(get_pg!()).await.unwrap()
}
pub async fn is_expired_at(&self, time: i64) -> bool {
if let Some(expiry) = get_itemdb!()
.get_item(&self.item)
.unwrap()
.variant(&self.variant)
.unwrap()
.expiry
{
let date_added = self.created.timestamp();
let expiration_ts = expiry * 24 * 60 * 60;
return (date_added + expiration_ts) < time;
}
false
}
pub async fn is_expired_in_days(&self, days: i64) -> bool {
let current_time = chrono::Utc::now().timestamp();
self.is_expired_at(current_time + (days * 24 * 60 * 60))
.await
}
pub async fn is_expired(&self) -> bool {
if self.consumed_timestamp.is_some() {
if let Some(expiry) = get_itemdb!()
.get_item(&self.item)
.unwrap()
.variant(&self.variant)
.unwrap()
.expiry
{
let time_around =
self.created.timestamp() - self.consumed_timestamp.unwrap().timestamp();
let expiration_ts = expiry * 24 * 60 * 60;
return time_around > expiration_ts;
} else {
return false;
}
}
let current_time = chrono::Utc::now().timestamp();
self.is_expired_at(current_time).await
}
pub async fn in_location(l: &str) -> Vec<Self> {
sqlx::query_as(
"SELECT * FROM transactions WHERE location = $1 AND consumed_timestamp IS NULL",
)
.bind(l)
.fetch_all(get_pg!())
.await
.unwrap()
}
pub async fn in_location_recursive(l: &str) -> Option<Vec<Self>> {
// get the children of this location
let locations = get_locations!().get(l)?.children_recursive();
let mut transactions = Self::in_location(l).await;
for loc in locations {
transactions.extend(Self::in_location(&loc.id).await);
}
Some(transactions)
}
/// Get all Transactions which are not consumed and are expired
pub async fn active_expired(days: Option<i64>) -> Vec<Self> {
let items: Vec<Self> = sqlx::query_as(
"SELECT * FROM transactions WHERE consumed_timestamp IS NULL ORDER BY created DESC",
)
.fetch_all(get_pg!())
.await
.unwrap();
let expired_items: Vec<_> = futures::stream::iter(items)
.filter_map(|item| async move {
let expired = if let Some(days) = days {
item.is_expired_in_days(days).await
} else {
item.is_expired().await
};
if expired {
Some(item)
} else {
None
}
})
.collect()
.await;
expired_items
}
}
impl ToAPI for Transaction {
async fn api(&self) -> serde_json::Value {
let location = if let Some(loc) = &self.location {
Some(get_locations!().get(loc).unwrap().api().await)
} else {
None
};
let consumed = if self.consumed_timestamp.is_some() {
Some(json!({
"destination": self.destination,
"price": self.consumed_price,
"timestamp": self.consumed_timestamp.unwrap().timestamp()
}))
} else {
None
};
json!({
"uuid": self.id,
"item": self.item,
"variant": self.variant,
"price": self.price,
"origin": self.origin,
"location": location,
"timestamp": self.created.timestamp(),
"consumed": consumed,
"note": self.note,
"expired": self.is_expired().await
})
}
}