use mongod::Model; use mongodb::bson::doc; use serde::{Deserialize, Serialize}; use serde_json::json; use crate::transaction::{Price, Transaction}; pub fn sort_by_timestamp() -> mongodb::bson::Document { doc! { "timestamp": mongod::Sort::Descending } } pub fn timestamp_range(year: i32, month: u32) -> (i64, i64) { let d = chrono::NaiveDate::from_ymd_opt(year, month, 0).unwrap(); let t = chrono::NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap(); let start = chrono::NaiveDateTime::new(d, t).and_utc().timestamp(); assert!(month <= 12); let end = if month == 12 { let d = chrono::NaiveDate::from_ymd_opt(year + 1, month, 0).unwrap(); let t = chrono::NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap(); chrono::NaiveDateTime::new(d, t).and_utc().timestamp() } else { let d = chrono::NaiveDate::from_ymd_opt(year, month + 1, 0).unwrap(); let t = chrono::NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap(); chrono::NaiveDateTime::new(d, t).and_utc().timestamp() }; (start, end) } /// Represents a specific instance of an item with potential variations. /// /// This struct is used to describe a particular variation or instance of an item /// in the real world. It may include attributes or properties that deviate from /// the standard definition of the item. For example, different colors, sizes, or /// configurations. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Variant { /// Associated Item pub item: String, /// Variant ID pub variant: String, /// Variant Name pub name: String, } impl Variant { /// Create variant from itemdb yaml pub fn from_yml(json: &serde_yaml::Value, variant: &str, item: &str) -> Self { Self { item: item.to_string(), variant: variant.to_string(), name: json .as_mapping() .unwrap() .get("name") .unwrap() .as_str() .unwrap() .to_string(), } } pub async fn supply_log(&self) -> Vec { let filter = doc! { "item": &self.item, "variant": &self.variant }; let result = Transaction::find_partial(filter, json!({}), None, None) .await .unwrap(); let mut ret = Vec::new(); for doc in result { ret.push(doc._id); } ret } pub async fn inventory(&self) -> Vec { let filter = doc! { "item": &self.item, "variant": &self.variant, "consumed": { "$exists": false } }; Transaction::find(filter, None, None).await.unwrap() } pub async fn demand_log(&self) -> Vec { let filter = doc! { "item": &self.item, "variant": &self.variant, "consumed": { "$exists": true } }; let result = Transaction::find_partial(filter, json!({}), None, None) .await .unwrap(); let mut ret = Vec::new(); for doc in result { ret.push(doc._id); } ret } pub async fn demand(uuid: &str, price: Price, destination: &str) -> Option<()> { // check if transaction exists let mut t = Transaction::get(uuid).await?; t.consume(price, destination).await; Some(()) } /// Records a supply transaction in the database. /// /// # Arguments /// /// * `price` - The price of the supplied items. /// * `origin` - The origin or source of the supplied items. /// /// # Returns /// /// Returns a UUID string representing the transaction. pub async fn supply(&self, price: Price, origin: Option<&str>) -> String { let t = Transaction::new(&self.item, &self.variant, price, origin); t.insert().await.unwrap(); t._id } pub async fn get_all_transactions(&self) -> Vec { let filter = doc! { "item": &self.item, "variant": &self.variant }; Transaction::find(filter, None, None).await.unwrap() } pub async fn get_transaction_timeslice(&self, year: i32, month: u32) -> Vec { let (start, end) = timestamp_range(year, month); Transaction::find( doc! { "timestamp": { "$gte": start, "$lte": end } }, None, Some(sort_by_timestamp()), ) .await .unwrap() } pub async fn get_unique_origins(&self) -> Vec { Transaction::unique( doc! { "item": &self.item, "variant": &self.variant }, "origin", ) .await } pub async fn get_unique_destinations(&self) -> Vec { Transaction::unique( doc! { "item": &self.item, "variant": &self.variant }, "consumed.destination", ) .await } pub async fn get_latest_price(&self, origin: Option) -> Price { let mut filter = doc! { "item": &self.item, "variant": &self.variant }; if let Some(origin) = origin { filter.insert("origin", origin); } Transaction::find(filter, Some(1), Some(sort_by_timestamp())) .await .unwrap() .first() .unwrap() .price .clone() } pub async fn stat(&self) -> serde_json::Value { let active_transactions = self.inventory().await; // fix : ignores currency let total_price: f64 = active_transactions.iter().map(|x| x.price.value).sum(); json!({ "amount": active_transactions.len(), "total_price": total_price }) } pub fn api_json(&self) -> serde_json::Value { json!({ "item": self.item, "variant": self.variant, "name": self.name }) } }