Skip to main content

kasl/commands/
sum.rs

1//! Monthly working hours summary command.
2//!
3//! Generates comprehensive monthly reports showing daily work hours, productivity metrics, and calendar integration with company rest days.
4//!
5//! ## Usage
6//!
7//! ```bash
8//! # Generate current month summary
9//! kasl sum
10//!
11//! # Submit monthly report to external API
12//! kasl sum --send
13//! ```
14
15use crate::{
16    api::si::Si,
17    db::{pauses::Pauses, workdays::Workdays},
18    libs::{
19        config::Config,
20        messages::Message,
21        summary::{DailySummary, SummaryCalculator, SummaryFormatter},
22        view::View,
23    },
24    msg_error, msg_print,
25};
26use anyhow::Result;
27use chrono::{Datelike, Duration, Local, NaiveDate};
28use clap::Args;
29use std::collections::HashSet;
30
31/// Command-line arguments for the monthly summary command.
32///
33/// Currently supports basic summary generation with optional report submission.
34/// Future versions may add date range selection and detailed filtering options.
35#[derive(Debug, Args)]
36pub struct SumArgs {
37    /// Submit the monthly summary report
38    ///
39    /// When specified, the generated summary will be submitted to the configured
40    /// reporting API in addition to being displayed locally. This is useful for
41    /// organizational reporting requirements.
42    #[arg(long, help = "Send report")]
43    send: bool,
44}
45
46/// Generates and displays a comprehensive monthly working hours summary.
47///
48/// Creates a detailed analysis of work patterns for the current month, including
49/// productivity calculations, rest day integration, and daily breakdowns.
50///
51pub async fn cmd(_sum_args: SumArgs) -> Result<()> {
52    let now = Local::now();
53    let config = Config::read()?;
54    let monitor_config = config.monitor.clone().unwrap_or_default();
55
56    // Display header with current month and year
57    msg_print!(Message::WorkingHoursForMonth(now.format("%B, %Y").to_string()), true);
58
59    // Step 1: Fetch company rest dates from external API if configured
60    let mut rest_dates: HashSet<NaiveDate> = HashSet::new();
61    if let Some(si_config) = config.si {
62        match Si::new(&si_config).rest_dates(now.date_naive()).await {
63            Ok(dates) => {
64                // Filter rest dates to only include current month
65                rest_dates = dates.into_iter().filter(|d| d.month() == now.month()).collect();
66            }
67            Err(e) => {
68                // Log error but continue with local data only
69                msg_error!(Message::ErrorRequestingRestDates(e.to_string()));
70            }
71        }
72    }
73
74    // Step 2: Fetch all workdays for the current month from local database
75    let workdays = Workdays::new()?.fetch_month(now.date_naive())?;
76    let workdays_count = workdays.len() as f64;
77    let mut daily_summaries = Vec::new();
78    let mut total_productivity = 0.0;
79
80    // Step 3: Process each workday to calculate durations and productivity
81    for workday in workdays {
82        // Now only while the day is still today; an unclosed past day ends at
83        // its last observed activity - see report::workday_end_time.
84        let workday_pauses = Pauses::new()?.get_workday_pauses(&workday)?;
85        let end_time = crate::libs::report::workday_end_time(&workday, &workday_pauses);
86        let gross_duration = end_time.signed_duration_since(workday.start);
87
88        // Note: All pauses data now handled by Productivity module
89
90        // Fetch filtered long breaks for display purposes
91        let long_breaks_duration = Pauses::new()?
92            .set_min_duration(monitor_config.min_pause_duration)
93            .get_workday_pauses(&workday)?
94            .iter()
95            .filter_map(|b| b.duration)
96            .fold(Duration::zero(), |acc, d| acc + d);
97
98        // Calculate display duration (gross time minus long breaks only)
99        let gross_work_time_minus_long_breaks = gross_duration - long_breaks_duration;
100
101        // Note: Net working duration calculation now handled by Productivity module
102
103        // Calculate productivity using centralized module for consistency
104        // This uses the same comprehensive calculation logic used throughout the app
105        let productivity = crate::libs::productivity::Productivity::new(&workday)
106            .map(|p| p.calculate_productivity())
107            .unwrap_or(0.0);
108
109        // Accumulate productivity for monthly average calculation
110        total_productivity += productivity;
111
112        // Create daily summary entry
113        daily_summaries.push(DailySummary {
114            date: workday.date,
115            duration: gross_work_time_minus_long_breaks, // Display duration
116            productivity,
117        });
118    }
119
120    // Step 4: Integrate rest dates and calculate summary statistics
121    let event_summary = daily_summaries
122        .add_rest_dates(rest_dates, Duration::hours(8)) // Default 8 hours for rest days
123        .calculate_totals()
124        .format_summary();
125
126    // Step 5: Display the formatted summary table
127    View::sum(&event_summary)?;
128
129    // Step 6: Display monthly productivity average
130    if total_productivity > 0.0 && workdays_count > 0.0 {
131        let average_productivity = total_productivity / workdays_count;
132        msg_print!(Message::MonthlyProductivity(average_productivity), true);
133    }
134
135    Ok(())
136}