2024-09-27 09:19:32 +02:00
use std ::collections ::HashMap ;
2024-07-24 16:38:56 +02:00
use serde ::{ Deserialize , Serialize } ;
2024-06-22 02:05:22 +02:00
use serde_json ::json ;
2024-05-03 18:22:59 +02:00
2024-10-07 20:53:58 +02:00
use crate ::{ get_pg , transaction ::Transaction } ;
2024-08-28 09:14:33 +02:00
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 )
}
2024-05-03 18:22:59 +02:00
/// 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.
2024-07-24 16:38:56 +02:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq) ]
2024-05-03 18:22:59 +02:00
pub struct Variant {
2024-05-10 11:59:05 +02:00
/// Associated Item
2024-05-03 18:22:59 +02:00
pub item : String ,
2024-08-28 09:38:10 +02:00
/// Variant ID
2024-05-03 18:22:59 +02:00
pub variant : String ,
2024-08-28 09:38:10 +02:00
/// Variant Name
pub name : String ,
2024-08-30 14:13:56 +02:00
/// Minimum amount
pub min : Option < i64 > ,
/// Days until expiry
pub expiry : Option < i64 > ,
2024-10-08 10:42:21 +02:00
/// Associated barcodes
pub barcodes : Option < Vec < i64 > >
2024-05-03 18:22:59 +02:00
}
impl Variant {
2024-05-10 11:59:05 +02:00
/// Create variant from itemdb yaml
2024-05-06 08:24:23 +02:00
pub fn from_yml ( json : & serde_yaml ::Value , variant : & str , item : & str ) -> Self {
Self {
item : item . to_string ( ) ,
variant : variant . to_string ( ) ,
2024-08-28 09:38:10 +02:00
name : json
2024-05-06 08:24:23 +02:00
. as_mapping ( )
. unwrap ( )
2024-08-28 09:38:10 +02:00
. get ( " name " )
. unwrap ( )
. as_str ( )
2024-05-10 10:56:07 +02:00
. unwrap ( )
2024-08-28 09:38:10 +02:00
. to_string ( ) ,
2024-08-30 14:13:56 +02:00
min : json
. as_mapping ( )
. unwrap ( )
. get ( " min " )
. map ( | x | x . as_i64 ( ) . unwrap ( ) ) ,
expiry : json
. as_mapping ( )
. unwrap ( )
. get ( " expiry " )
. map ( | x | x . as_i64 ( ) . unwrap ( ) ) ,
2024-10-08 10:42:21 +02:00
barcodes : json . as_mapping ( ) . unwrap ( ) . get ( " barcodes " ) . map ( | x | {
x . as_sequence ( ) . unwrap ( ) . into_iter ( ) . map ( | x | x . as_i64 ( ) . unwrap ( ) ) . collect ( )
} )
2024-05-06 08:24:23 +02:00
}
}
2024-09-13 14:17:55 +02:00
pub fn item_variant_id ( & self ) -> String {
format! ( " {} :: {} " , self . item , self . variant )
}
2024-09-12 13:11:44 +02:00
/// Returns the IDs of Transactions from this Item Variant.
2024-05-10 11:59:05 +02:00
pub async fn supply_log ( & self ) -> Vec < String > {
2024-10-07 20:53:58 +02:00
let res : Vec < ( uuid ::Uuid , ) > = sqlx ::query_as (
" SELECT id FROM transactions WHERE item = $1 AND variant = $2 ORDER BY created DESC " ,
)
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) )
. await
. unwrap ( ) ;
2024-05-10 11:59:05 +02:00
2024-10-07 20:53:58 +02:00
res . into_iter ( ) . map ( | x | x . 0. to_string ( ) ) . collect ( )
2024-05-10 11:59:05 +02:00
}
2024-09-12 13:11:44 +02:00
/// Returns the active Transaction of this Item Variant which are not yet consumed.
2024-08-13 04:13:38 +02:00
pub async fn inventory ( & self ) -> Vec < Transaction > {
2024-10-07 20:53:58 +02:00
sqlx ::query_as ( " SELECT * FROM transactions WHERE item = $1 AND variant = $2 AND consumed_timestamp IS NULL ORDER BY created DESC " )
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( )
2024-08-13 04:13:38 +02:00
}
2024-09-12 13:11:44 +02:00
/// Returns the IDs of the Transactions from this Item Variant which are consumed.
pub async fn demand_log ( & self , destination : Option < & str > ) -> Vec < String > {
2024-10-07 20:53:58 +02:00
let res : Vec < ( uuid ::Uuid , ) > = if let Some ( destination ) = destination {
sqlx ::query_as (
" SELECT id FROM transactions WHERE item = $1 AND variant = $2 AND consumed_timestamp IS NOT NULL AND destination = $3 ORDER BY created DESC "
)
. bind ( & self . item )
. bind ( & self . variant )
. bind ( destination )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( )
2024-09-12 13:11:44 +02:00
} else {
2024-10-07 20:53:58 +02:00
sqlx ::query_as (
" SELECT id FROM transactions WHERE item = $1 AND variant = $2 AND consumed_timestamp IS NOT NULL ORDER BY created DESC "
)
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( )
2024-05-10 11:59:05 +02:00
} ;
2024-10-07 20:53:58 +02:00
res . into_iter ( ) . map ( | x | x . 0. to_string ( ) ) . collect ( )
2024-05-10 11:59:05 +02:00
}
2024-10-07 20:53:58 +02:00
pub async fn demand ( uuid : & uuid ::Uuid , price : f64 , destination : & str ) -> Option < Transaction > {
2024-07-24 16:38:56 +02:00
// check if transaction exists
2024-10-07 20:53:58 +02:00
let t = Transaction ::get ( uuid ) . await ? ;
Some ( t . consume ( price , destination ) . await )
2024-05-03 18:22:59 +02:00
}
/// 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.
2024-09-02 18:40:02 +02:00
pub async fn supply (
& self ,
2024-10-07 20:53:58 +02:00
price : f64 ,
2024-09-02 18:40:02 +02:00
origin : Option < & str > ,
location : Option < & str > ,
2024-09-21 01:38:22 +02:00
note : Option < & str > ,
2024-09-12 11:48:09 +02:00
) -> Transaction {
2024-10-07 20:53:58 +02:00
Transaction ::new ( & self . item , & self . variant , price , origin , location , note ) . await
2024-05-03 18:22:59 +02:00
}
2024-05-10 10:56:07 +02:00
2024-09-12 13:11:44 +02:00
/// Returns all Transactions of this Item Variant
2024-08-28 09:14:33 +02:00
pub async fn get_all_transactions ( & self ) -> Vec < Transaction > {
2024-10-07 20:53:58 +02:00
sqlx ::query_as (
" SELECT * FROM transactions WHERE item = $1 AND variant = $2 ORDER BY created DESC " ,
)
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) )
. await
. unwrap ( )
2024-08-28 09:14:33 +02:00
}
pub async fn get_transaction_timeslice ( & self , year : i32 , month : u32 ) -> Vec < Transaction > {
let ( start , end ) = timestamp_range ( year , month ) ;
2024-10-07 20:53:58 +02:00
sqlx ::query_as ( " SELECT * FROM transactions WHERE created BETWEEN to_timestamp($1) AND to_timestamp($2) ORDER BY created DESC " )
. bind ( start )
. bind ( end )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( )
2024-08-28 09:14:33 +02:00
}
pub async fn get_unique_origins ( & self ) -> Vec < String > {
2024-10-07 20:53:58 +02:00
let res : Vec < ( String , ) > = sqlx ::query_as ( " SELECT DISTINCT(origin) FROM transactions WHERE origin NOT LIKE 'flow::%' AND item = $1 AND variant = $2 " )
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( ) ;
res . into_iter ( ) . map ( | x | x . 0 ) . collect ( )
2024-08-28 09:14:33 +02:00
}
pub async fn get_unique_destinations ( & self ) -> Vec < String > {
2024-10-07 20:53:58 +02:00
let res : Vec < ( String , ) > = sqlx ::query_as ( " SELECT DISTINCT(destination) FROM transactions WHERE destination NOT LIKE 'flow::%' AND item = $1 AND variant = $2 " )
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( ) ;
res . into_iter ( ) . map ( | x | x . 0 ) . collect ( )
2024-08-28 09:14:33 +02:00
}
2024-10-07 20:53:58 +02:00
pub async fn price_history_by_origin ( & self , origin : & str , limit : Option < i64 > ) -> Vec < f64 > {
let res : Vec < ( f64 , ) > = sqlx ::query_as (
& format! ( " SELECT price FROM transactions WHERE item = $1 AND variant = $2 AND origin = $3 ORDER BY created DESC {} " , if let Some ( limit ) = limit {
format! ( " LIMIT {limit} " )
} else { String ::new ( ) } )
2024-08-30 14:13:56 +02:00
)
2024-10-07 20:53:58 +02:00
. bind ( & self . item )
. bind ( & self . variant )
. bind ( origin )
. fetch_all ( get_pg! ( ) ) . await . unwrap ( ) ;
res . into_iter ( ) . map ( | x | x . 0 ) . collect ( )
2024-08-30 14:13:56 +02:00
}
2024-10-07 20:53:58 +02:00
pub async fn get_latest_price ( & self , origin : Option < String > ) -> f64 {
2024-08-28 09:14:33 +02:00
if let Some ( origin ) = origin {
2024-10-07 20:53:58 +02:00
let res : ( f64 , ) = sqlx ::query_as ( " SELECT price FROM transactions WHERE item = $1 AND variant = $2 AND origin = $3 ORDER BY created DESC LIMIT 1 " )
. bind ( & self . item )
. bind ( & self . variant )
. bind ( origin )
. fetch_one ( get_pg! ( ) ) . await . unwrap ( ) ;
res . 0
} else {
let res : ( f64 , ) = sqlx ::query_as ( " SELECT price FROM transactions WHERE item = $1 AND variant = $2 ORDER BY created DESC LIMIT 1 " )
. bind ( & self . item )
. bind ( & self . variant )
. bind ( origin )
. fetch_one ( get_pg! ( ) ) . await . unwrap ( ) ;
res . 0
2024-08-28 09:14:33 +02:00
}
}
2024-09-12 10:58:49 +02:00
/// Check if item variant is below minimum. Returns if this is the case and the number needed to fulfill minimum
pub async fn is_below_min ( & self ) -> ( bool , i64 ) {
if let Some ( min ) = self . min {
let amount = self . inventory ( ) . await . len ( ) as i64 ;
if amount < min {
return ( true , min - amount ) ;
}
}
( false , 0 )
}
2024-09-27 09:19:32 +02:00
pub async fn stat ( & self , full : bool ) -> serde_json ::Value {
2024-08-27 23:33:09 +02:00
let active_transactions = self . inventory ( ) . await ;
2024-10-07 20:53:58 +02:00
let total_price : f64 = active_transactions . iter ( ) . map ( | x | x . price ) . sum ( ) ;
2024-08-27 23:33:09 +02:00
2024-09-27 09:19:32 +02:00
if ! full {
return json! ( {
" amount " : active_transactions . len ( ) ,
" total_price " : total_price
} ) ;
}
2024-10-07 20:53:58 +02:00
let all_transactions : Vec < Transaction > = sqlx ::query_as (
" SELECT * FROM transactions WHERE item = $1 AND variant = $2 ORDER BY created DESC " ,
2024-09-27 09:19:32 +02:00
)
2024-10-07 20:53:58 +02:00
. bind ( & self . item )
. bind ( & self . variant )
. fetch_all ( get_pg! ( ) )
2024-09-27 09:19:32 +02:00
. await
. unwrap ( ) ;
2024-10-07 20:53:58 +02:00
2024-09-27 09:19:32 +02:00
let mut expired_count = 0.0 ;
for t in & all_transactions {
if t . is_expired ( ) . await {
expired_count + = 1.0 ;
}
}
let expiry_rate = expired_count / all_transactions . len ( ) as f64 ;
let mut origin_stat = HashMap ::new ( ) ;
for origin in self . get_unique_origins ( ) . await {
let transactions_from_origin = active_transactions
. iter ( )
. filter ( | x | x . origin . as_ref ( ) . map ( | x | * x = = origin ) . unwrap_or ( false ) )
. collect ::< Vec < _ > > ( ) ;
let prices = self
. price_history_by_origin ( & origin , None )
. await
. into_iter ( )
2024-10-07 20:53:58 +02:00
. map ( | x | x )
2024-09-27 09:19:32 +02:00
. collect ::< Vec < _ > > ( ) ;
let prices_len = prices . len ( ) as f64 ;
let prices_summed = prices . into_iter ( ) . reduce ( | acc , e | acc + e ) . unwrap_or ( 0.0 ) ;
let stat_json = json! ( {
" average_price " : prices_summed / prices_len ,
" inventory " : transactions_from_origin . len ( )
} ) ;
origin_stat . insert ( origin , stat_json ) ;
}
2024-08-27 23:33:09 +02:00
json! ( {
2024-09-27 09:19:32 +02:00
" amount " : active_transactions . len ( ) ,
" total_price " : total_price ,
" expiry_rate " : expiry_rate ,
" origins " : origin_stat
2024-08-27 23:33:09 +02:00
} )
}
2024-06-22 02:05:22 +02:00
pub fn api_json ( & self ) -> serde_json ::Value {
json! ( {
" item " : self . item ,
" variant " : self . variant ,
2024-08-30 14:13:56 +02:00
" name " : self . name ,
" min " : self . min ,
2024-10-08 10:42:21 +02:00
" expiry " : self . expiry ,
" barcodes " : self . barcodes
2024-06-22 02:05:22 +02:00
} )
}
2024-05-03 18:22:59 +02:00
}