use crate::model::Transaction; use chrono::NaiveDate; use std::fs::File; pub fn parse(filename: &str) -> anyhow::Result> { let file = File::open(filename)?; let mut reader = csv::ReaderBuilder::new() .has_headers(true) .from_reader(file); let mut transactions = Vec::new(); for result in reader.records() { let record = result?; if record.len() >= 4 { // Format: Date, Transaction Details, Funds Out, Funds In if let (Ok(date), Some(description)) = ( NaiveDate::parse_from_str(record[0].trim(), "%m/%d/%Y"), record.get(1), ) { let account = "Simplii Financial".to_string(); // Check Funds Out (expenses) if !record[2].trim().is_empty() { if let Ok(amount) = record[2].parse::() { transactions.push(Transaction::new( date, description.trim().to_string(), -amount, account.clone(), )); } } // Check Funds In (income/deposits) if !record[3].trim().is_empty() { if let Ok(amount) = record[3].parse::() { transactions.push(Transaction::new( date, description.trim().to_string(), amount, account.clone(), )); } } } } } Ok(transactions) } #[cfg(test)] mod tests { use super::*; use std::io::Write; use tempfile::NamedTempFile; #[test] fn test_simplii_parser() { let csv_content = "Date, Transaction Details, Funds Out, Funds In \n05/31/2024, TRANSFER IN,,1205.61\n05/31/2024, TRANSFER OUT,1200.00,\n06/04/2024,EFT DEBIT EMBARK STUDENT FOUNDATION CHEQUE,25.00,"; let mut temp_file = NamedTempFile::new().unwrap(); temp_file.write_all(csv_content.as_bytes()).unwrap(); temp_file.flush().unwrap(); let transactions = parse(temp_file.path().to_str().unwrap()).unwrap(); assert_eq!(transactions.len(), 3); // Transfer In (income) let transfer_in = &transactions[0]; assert_eq!( transfer_in.date.format("%Y-%m-%d").to_string(), "2024-05-31" ); assert_eq!(transfer_in.description.trim(), "TRANSFER IN"); assert_eq!(transfer_in.amount, 1205.61); assert_eq!(transfer_in.account, "Simplii Financial"); assert_eq!(transfer_in.category, Some("Transfer".to_string())); // Transfer Out (expense) let transfer_out = &transactions[1]; assert_eq!( transfer_out.date.format("%Y-%m-%d").to_string(), "2024-05-31" ); assert_eq!(transfer_out.description.trim(), "TRANSFER OUT"); assert_eq!(transfer_out.amount, -1200.00); assert_eq!(transfer_out.account, "Simplii Financial"); assert_eq!(transfer_out.category, Some("Transfer".to_string())); // EFT Debit (expense) let eft_debit = &transactions[2]; assert_eq!(eft_debit.date.format("%Y-%m-%d").to_string(), "2024-06-04"); assert_eq!( eft_debit.description.trim(), "EFT DEBIT EMBARK STUDENT FOUNDATION CHEQUE" ); assert_eq!(eft_debit.amount, -25.00); assert_eq!(eft_debit.account, "Simplii Financial"); // This gets categorized as "Other" since it doesn't match specific patterns assert!(eft_debit.category.is_some()); } #[test] fn test_simplii_parser_empty_amounts() { let csv_content = "Date, Transaction Details, Funds Out, Funds In \n05/31/2024, EMPTY TRANSACTION,,"; let mut temp_file = NamedTempFile::new().unwrap(); temp_file.write_all(csv_content.as_bytes()).unwrap(); temp_file.flush().unwrap(); let transactions = parse(temp_file.path().to_str().unwrap()).unwrap(); assert_eq!(transactions.len(), 0); } }