Files
outlay/outlay-core/src/notifications.rs
lashman 10a76e3003 Add feature batch 2, subscription/recurring sync, smooth charts, and app icon
- Implement subscriptions view with bidirectional recurring transaction sync
- Add cascade delete/pause/resume between subscriptions and recurring
- Fix foreign key constraints when deleting recurring transactions
- Add cross-view instant refresh via callback pattern
- Replace Bezier chart smoothing with Fritsch-Carlson monotone Hermite interpolation
- Smooth budget sparklines using shared monotone_subdivide function
- Add vertical spacing to budget rows
- Add app icon (receipt on GNOME blue) in all sizes for desktop, web, and AppImage
- Add calendar, credit cards, forecast, goals, insights, and wishlist views
- Add date picker, numpad, quick-add, category combo, and edit dialog components
- Add import/export for CSV, JSON, OFX, QIF formats
- Add NLP transaction parsing, OCR receipt scanning, expression evaluator
- Add notification support, Sankey chart, tray icon
- Add demo data seeder with full DB wipe
- Expand database schema with subscriptions, goals, credit cards, and more
2026-03-03 21:18:37 +02:00

77 lines
2.3 KiB
Rust

use crate::db::Database;
use std::process::Command;
/// Send a desktop notification via notify-send (Linux).
/// Returns silently if notify-send is not available.
pub fn send_notification(title: &str, body: &str, urgency: &str) {
let _ = Command::new("notify-send")
.arg("--urgency")
.arg(urgency)
.arg("--app-name=Outlay")
.arg(title)
.arg(body)
.spawn();
}
/// Check all budgets for the given month and send notifications
/// for any thresholds crossed that haven't been notified yet.
/// Only sends if budget_notifications setting is enabled.
pub fn check_and_send_budget_notifications(db: &Database, month: &str) {
let enabled = db.get_setting("budget_notifications")
.ok().flatten().map(|s| s == "1").unwrap_or(false);
if !enabled {
return;
}
let budgets = match db.list_budgets_for_month(month) {
Ok(b) => b,
Err(_) => return,
};
for budget in &budgets {
let cat_name = db.get_category(budget.category_id)
.map(|c| c.name)
.unwrap_or_else(|_| "Unknown".to_string());
let thresholds = match db.check_budget_thresholds(budget.category_id, month) {
Ok(t) => t,
Err(_) => continue,
};
for threshold in &thresholds {
let (title, urgency) = match threshold {
100 => (
format!("Budget exceeded: {}", cat_name),
"critical",
),
_ => (
format!("Budget {}% used: {}", threshold, cat_name),
"normal",
),
};
let progress = db.get_budget_progress(budget.category_id, month)
.ok().flatten();
let body = if let Some((budget_amt, spent, pct)) = progress {
format!("{:.2} of {:.2} spent ({:.0}%)", spent, budget_amt, pct)
} else {
String::new()
};
send_notification(&title, &body, urgency);
let _ = db.record_notification(budget.category_id, month, *threshold);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_send_notification_does_not_panic() {
// Should not panic even if notify-send is not installed
send_notification("Test", "Body", "normal");
}
}