use chrono::NaiveDate; use serde::{Deserialize, Serialize}; use std::fmt; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum TransactionType { Expense, Income, } impl TransactionType { pub fn as_str(&self) -> &'static str { match self { TransactionType::Expense => "expense", TransactionType::Income => "income", } } pub fn from_str(s: &str) -> Option { match s { "expense" => Some(TransactionType::Expense), "income" => Some(TransactionType::Income), _ => None, } } } impl fmt::Display for TransactionType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Frequency { Daily, Weekly, Biweekly, Monthly, Yearly, } impl Frequency { pub fn as_str(&self) -> &'static str { match self { Frequency::Daily => "daily", Frequency::Weekly => "weekly", Frequency::Biweekly => "biweekly", Frequency::Monthly => "monthly", Frequency::Yearly => "yearly", } } pub fn from_str(s: &str) -> Option { match s { "daily" => Some(Frequency::Daily), "weekly" => Some(Frequency::Weekly), "biweekly" => Some(Frequency::Biweekly), "monthly" => Some(Frequency::Monthly), "yearly" => Some(Frequency::Yearly), _ => None, } } } impl fmt::Display for Frequency { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Category { pub id: i64, pub name: String, pub icon: Option, pub color: Option, pub transaction_type: TransactionType, pub is_default: bool, pub sort_order: i32, pub parent_id: Option, } #[derive(Debug, Clone)] pub struct NewCategory { pub name: String, pub icon: Option, pub color: Option, pub transaction_type: TransactionType, pub sort_order: i32, pub parent_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Transaction { pub id: i64, pub amount: f64, pub transaction_type: TransactionType, pub category_id: i64, pub currency: String, pub exchange_rate: f64, pub note: Option, pub date: NaiveDate, pub created_at: String, pub recurring_id: Option, pub payee: Option, } #[derive(Debug, Clone)] pub struct NewTransaction { pub amount: f64, pub transaction_type: TransactionType, pub category_id: i64, pub currency: String, pub exchange_rate: f64, pub note: Option, pub date: NaiveDate, pub recurring_id: Option, pub payee: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Budget { pub id: i64, pub category_id: i64, pub amount: f64, pub month: String, pub rollover: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RecurringTransaction { pub id: i64, pub amount: f64, pub transaction_type: TransactionType, pub category_id: i64, pub currency: String, pub note: Option, pub frequency: Frequency, pub start_date: NaiveDate, pub end_date: Option, pub last_generated: Option, pub active: bool, pub resume_date: Option, pub is_bill: bool, pub reminder_days: i32, pub subscription_id: Option, } #[derive(Debug, Clone)] pub struct NewRecurringTransaction { pub amount: f64, pub transaction_type: TransactionType, pub category_id: i64, pub currency: String, pub note: Option, pub frequency: Frequency, pub start_date: NaiveDate, pub end_date: Option, pub is_bill: bool, pub reminder_days: i32, pub subscription_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExchangeRate { pub base: String, pub target: String, pub rate: f64, pub fetched_at: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Tag { pub id: i64, pub name: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Split { pub id: i64, pub transaction_id: i64, pub category_id: i64, pub amount: f64, pub note: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransactionTemplate { pub id: i64, pub name: String, pub amount: Option, pub transaction_type: TransactionType, pub category_id: i64, pub currency: String, pub payee: Option, pub note: Option, pub tags: Option, pub sort_order: i32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CategorizeRule { pub id: i64, pub field: String, pub pattern: String, pub category_id: i64, pub priority: i32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SavingsGoal { pub id: i64, pub name: String, pub target: f64, pub saved: f64, pub currency: String, pub deadline: Option, pub color: Option, pub icon: Option, pub created_at: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WishlistItem { pub id: i64, pub name: String, pub amount: f64, pub category_id: Option, pub url: Option, pub note: Option, pub priority: i32, pub purchased: bool, pub created_at: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubscriptionCategory { pub id: i64, pub name: String, pub icon: Option, pub color: Option, pub sort_order: i32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Subscription { pub id: i64, pub name: String, pub amount: f64, pub currency: String, pub frequency: Frequency, pub category_id: i64, pub start_date: NaiveDate, pub next_due: NaiveDate, pub active: bool, pub note: Option, pub url: Option, pub recurring_id: Option, } #[derive(Debug, Clone)] pub struct NewSubscription { pub name: String, pub amount: f64, pub currency: String, pub frequency: Frequency, pub category_id: i64, pub start_date: NaiveDate, pub note: Option, pub url: Option, pub recurring_id: Option, } #[derive(Debug, Clone)] pub struct CreditCard { pub id: i64, pub name: String, pub credit_limit: Option, pub statement_close_day: i32, pub due_day: i32, pub min_payment_pct: f64, pub current_balance: f64, pub currency: String, pub color: Option, pub active: bool, } #[derive(Debug, Clone)] pub struct NewCreditCard { pub name: String, pub credit_limit: Option, pub statement_close_day: i32, pub due_day: i32, pub min_payment_pct: f64, pub currency: String, pub color: Option, } #[derive(Debug, Clone)] pub struct Achievement { pub id: i64, pub name: String, pub description: String, pub earned_at: Option, pub icon: Option, } #[derive(Debug, Clone)] pub struct ParsedTransaction { pub amount: f64, pub category_name: Option, pub category_id: Option, pub note: Option, pub payee: Option, pub transaction_type: TransactionType, } #[derive(Debug, Clone)] pub struct SankeyNode { pub label: String, pub value: f64, pub color: (f64, f64, f64), pub y: f64, pub height: f64, } #[derive(Debug, Clone)] pub struct SankeyFlow { pub from_idx: usize, pub to_idx: usize, pub value: f64, pub from_y: f64, pub to_y: f64, pub width: f64, } #[derive(Debug, Clone)] pub struct SankeyLayout { pub left_nodes: Vec, pub right_nodes: Vec, pub center_y: f64, pub center_height: f64, pub flows_in: Vec, pub flows_out: Vec, pub net: f64, } #[derive(Debug, Clone)] pub struct RecapCategory { pub category_name: String, pub category_icon: Option, pub category_color: Option, pub amount: f64, pub percentage: f64, pub change_pct: Option, } #[derive(Debug, Clone)] pub struct MonthlyRecap { pub total_income: f64, pub total_expenses: f64, pub net: f64, pub transaction_count: i64, pub categories: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BudgetCycleMode { Calendar, Payday, Rolling, } impl BudgetCycleMode { pub fn as_str(&self) -> &'static str { match self { BudgetCycleMode::Calendar => "calendar", BudgetCycleMode::Payday => "payday", BudgetCycleMode::Rolling => "rolling", } } pub fn from_str(s: &str) -> Self { match s { "payday" => BudgetCycleMode::Payday, "rolling" => BudgetCycleMode::Rolling, _ => BudgetCycleMode::Calendar, } } } impl fmt::Display for BudgetCycleMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } #[derive(Debug, Clone)] pub struct PdfParsedRow { pub date: Option, pub description: String, pub amount: f64, pub is_credit: bool, } #[cfg(test)] mod tests { use super::*; #[test] fn test_transaction_type_round_trip() { for tt in [TransactionType::Expense, TransactionType::Income] { let s = tt.as_str(); let parsed = TransactionType::from_str(s).unwrap(); assert_eq!(tt, parsed); } } #[test] fn test_transaction_type_invalid() { assert_eq!(TransactionType::from_str("bogus"), None); } #[test] fn test_frequency_round_trip() { for f in [ Frequency::Daily, Frequency::Weekly, Frequency::Biweekly, Frequency::Monthly, Frequency::Yearly, ] { let s = f.as_str(); let parsed = Frequency::from_str(s).unwrap(); assert_eq!(f, parsed); } } #[test] fn test_frequency_invalid() { assert_eq!(Frequency::from_str("quarterly"), None); } #[test] fn test_transaction_type_display() { assert_eq!(format!("{}", TransactionType::Expense), "expense"); assert_eq!(format!("{}", TransactionType::Income), "income"); } #[test] fn test_frequency_display() { assert_eq!(format!("{}", Frequency::Monthly), "monthly"); assert_eq!(format!("{}", Frequency::Biweekly), "biweekly"); } }