237 lines
6.2 KiB
Rust
237 lines
6.2 KiB
Rust
use futures::StreamExt;
|
|
use mongod::{
|
|
assert_reference_of,
|
|
derive::{Model, Referencable},
|
|
reference_of, Model, Referencable, Reference, Sort, Validate,
|
|
};
|
|
use mongodb::bson::doc;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::json;
|
|
|
|
use crate::{item::Item, location::Location};
|
|
|
|
/// A Transaction of an Item Variant
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)]
|
|
pub struct Transaction {
|
|
/// UUID
|
|
pub _id: String,
|
|
/// Associated Item
|
|
pub item: String,
|
|
/// Associated Variant
|
|
pub variant: String,
|
|
/// Price of obtaining the Item
|
|
pub price: Price,
|
|
/// Origin of the Item
|
|
pub origin: Option<String>,
|
|
/// The location of the Item
|
|
pub location: Option<Reference>,
|
|
/// Info on consumption of the Item
|
|
pub consumed: Option<Consumed>,
|
|
/// Notes on Transaction
|
|
pub note: Option<String>,
|
|
/// Timestamp of the Transaction
|
|
pub timestamp: i64,
|
|
}
|
|
|
|
impl Validate for Transaction {
|
|
async fn validate(&self) -> Result<(), String> {
|
|
if let Some(location) = &self.location {
|
|
assert_reference_of!(location, Location);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Information about consumed Items
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct Consumed {
|
|
/// Destination of the Item or who consumed it
|
|
pub destination: String,
|
|
/// Price the Item was exported or consumed at
|
|
pub price: Price,
|
|
/// Timestamp of Consumption
|
|
pub timestamp: i64,
|
|
}
|
|
|
|
impl Transaction {
|
|
pub async fn new(
|
|
item: &str,
|
|
variant: &str,
|
|
price: Price,
|
|
origin: Option<&str>,
|
|
location: Option<&str>,
|
|
note: Option<&str>,
|
|
) -> Self {
|
|
Self {
|
|
_id: uuid::Uuid::new_v4().to_string(),
|
|
item: item.to_string(),
|
|
variant: variant.to_string(),
|
|
price,
|
|
consumed: None,
|
|
origin: origin.map(std::string::ToString::to_string),
|
|
location: if let Some(location) = location {
|
|
reference_of!(Location, location)
|
|
} else {
|
|
None
|
|
},
|
|
note: note.map(|x| x.to_string()),
|
|
timestamp: chrono::Utc::now().timestamp(),
|
|
}
|
|
}
|
|
|
|
/// Consumes the Item with `price` and `destination`
|
|
pub async fn consume(self, price: Price, destination: &str) -> Self {
|
|
self.change()
|
|
.consumed(Some(Consumed {
|
|
destination: destination.to_string(),
|
|
price,
|
|
timestamp: chrono::Utc::now().timestamp(),
|
|
}))
|
|
.update()
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
pub async fn is_expired(&self) -> bool {
|
|
let current_time = chrono::Utc::now().timestamp();
|
|
|
|
if let Some(expiry) = Item::get(&self.item)
|
|
.await
|
|
.unwrap()
|
|
.variant(&self.variant)
|
|
.unwrap()
|
|
.expiry
|
|
{
|
|
let date_added = self.timestamp;
|
|
|
|
let expiration_ts = expiry * 24 * 60 * 60;
|
|
|
|
return (date_added + expiration_ts) < current_time;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
pub async fn in_location(l: &str) -> Option<Vec<Self>> {
|
|
let l = reference_of!(Location, l)?;
|
|
Some(
|
|
Self::find(
|
|
doc! { "location": l, "consumed": { "$not": { "$type": "object" } }},
|
|
None,
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap(),
|
|
)
|
|
}
|
|
|
|
pub async fn in_location_recursive(l: &str) -> Option<Vec<Self>> {
|
|
// get the children of this location
|
|
let locations = Location::get(l).await?.children_recursive().await;
|
|
|
|
let l = reference_of!(Location, l)?;
|
|
let mut transactions = Self::find(
|
|
doc! { "location": l, "consumed": { "$not": { "$type": "object" } },},
|
|
None,
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
for loc in locations {
|
|
transactions.extend(
|
|
Self::find(doc! { "location": loc.reference(), "consumed": { "$not": { "$type": "object" } }}, None, None)
|
|
.await
|
|
.unwrap(),
|
|
);
|
|
}
|
|
|
|
Some(transactions)
|
|
}
|
|
|
|
/// Get all Transactions which are not consumed and are expired
|
|
pub async fn active_expired() -> Vec<Self> {
|
|
let items = Self::find(
|
|
doc! {
|
|
"consumed": { "$not": { "$type": "object" } }
|
|
},
|
|
None,
|
|
Some(doc! { "timestamp": Sort::Descending }),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let expired_items: Vec<_> = futures::stream::iter(items)
|
|
.filter_map(|item| async move {
|
|
if item.is_expired().await {
|
|
Some(item)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
.await;
|
|
|
|
expired_items
|
|
}
|
|
}
|
|
|
|
impl mongod::ToAPI for Transaction {
|
|
async fn api(&self) -> serde_json::Value {
|
|
json!({
|
|
"uuid": self._id,
|
|
"item": self.item,
|
|
"variant": self.variant,
|
|
"price": self.price,
|
|
"origin": self.origin,
|
|
"location": self.location.as_ref().map(|x| x.id().to_string()),
|
|
"timestamp": self.timestamp,
|
|
"consumed": self.consumed,
|
|
"note": self.note,
|
|
"expired": self.is_expired().await
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Economic Price
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct Price {
|
|
/// Value of the currency
|
|
pub value: f64,
|
|
/// Kind of currency
|
|
pub currency: String,
|
|
}
|
|
|
|
impl Price {
|
|
pub fn new(value: f64, currency: &str) -> Self {
|
|
Self {
|
|
value,
|
|
currency: currency.to_string(),
|
|
}
|
|
}
|
|
|
|
pub fn zero() -> Self {
|
|
Self {
|
|
value: 0.00,
|
|
currency: String::new(),
|
|
}
|
|
}
|
|
|
|
fn parse(price: &str) -> Option<Self> {
|
|
let (value, currency) = price.split_once(' ')?;
|
|
|
|
Some(Self {
|
|
value: value.parse().ok()?,
|
|
currency: currency.to_string(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for Price {
|
|
type Error = ();
|
|
|
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
|
Self::parse(&value).ok_or(())
|
|
}
|
|
}
|