use crate::{
api::si::Si,
db::{pauses::Pauses, workdays::Workdays},
libs::{
config::Config,
json,
messages::Message,
output::{self, Format},
summary::{DailySummary, SummaryCalculator, SummaryFormatter},
view::View,
},
msg_error, msg_print,
};
use anyhow::Result;
use chrono::{Datelike, Duration, Local, NaiveDate};
use clap::Args;
use std::collections::HashSet;
#[derive(Debug, Args)]
pub struct SumArgs {
#[arg(long, help = "Send report", conflicts_with = "json")]
send: bool,
#[command(flatten)]
format: Format,
}
pub struct MonthSummary {
pub month: NaiveDate,
pub days: Vec<DailySummary>,
pub total: Duration,
pub average: Duration,
pub productivity: Option<f64>,
}
impl MonthSummary {
pub async fn read(today: NaiveDate) -> Result<MonthSummary> {
let config = Config::read()?;
let monitor_config = config.monitor.clone().unwrap_or_default();
let mut rest_dates: HashSet<NaiveDate> = HashSet::new();
if let Some(si_config) = config.si {
match Si::new(&si_config).rest_dates(today).await {
Ok(dates) => {
rest_dates = dates.into_iter().filter(|d| d.month() == today.month()).collect();
}
Err(e) => {
msg_error!(Message::ErrorRequestingRestDates(e.to_string()));
}
}
}
let workdays = Workdays::new()?.fetch_month(today)?;
let workdays_count = workdays.len() as f64;
let mut daily_summaries = Vec::new();
let mut total_productivity = 0.0;
for workday in workdays {
let workday_pauses = Pauses::new()?.get_workday_pauses(&workday)?;
let end_time = crate::libs::report::workday_end_time(&workday, &workday_pauses);
let gross_duration = end_time.signed_duration_since(workday.start);
let long_breaks_duration = Pauses::new()?
.set_min_duration(monitor_config.min_pause_duration)
.get_workday_pauses(&workday)?
.iter()
.filter_map(|b| b.duration)
.fold(Duration::zero(), |acc, d| acc + d);
let productivity = crate::libs::productivity::Productivity::new(&workday)
.map(|p| p.calculate_productivity())
.unwrap_or(0.0);
total_productivity += productivity;
daily_summaries.push(DailySummary {
date: workday.date,
duration: gross_duration - long_breaks_duration,
productivity,
rest_day: false,
});
}
let (days, total, average) = daily_summaries
.add_rest_dates(rest_dates, Duration::hours(8)) .calculate_totals();
let productivity = (total_productivity > 0.0 && workdays_count > 0.0).then(|| total_productivity / workdays_count);
Ok(MonthSummary {
month: today,
days,
total,
average,
productivity,
})
}
pub fn to_json(&self) -> json::Month {
json::Month {
month: self.month.format("%Y-%m").to_string(),
days: self
.days
.iter()
.map(|day| json::MonthDay {
date: day.date,
work_seconds: json::seconds(&day.duration),
productivity: json::percent(day.productivity),
rest_day: day.rest_day,
})
.collect(),
total_seconds: json::seconds(&self.total),
average_seconds: json::seconds(&self.average),
productivity: self.productivity.map(json::percent),
}
}
}
pub async fn cmd(args: SumArgs) -> Result<()> {
let now = Local::now();
let month = MonthSummary::read(now.date_naive()).await?;
if args.format.json {
return output::print_json(&month.to_json());
}
msg_print!(Message::WorkingHoursForMonth(now.format("%B, %Y").to_string()), true);
let event_summary = (month.days, month.total, month.average).format_summary();
View::sum(&event_summary)?;
if let Some(average_productivity) = month.productivity {
msg_print!(Message::MonthlyProductivity(average_productivity), true);
}
Ok(())
}