tempo_cli/models/
time_estimate.rs

1use chrono::{DateTime, NaiveDate, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct TimeEstimate {
6    pub id: Option<i64>,
7    pub project_id: i64,
8    pub task_name: String,
9    pub estimated_hours: f64,
10    pub actual_hours: Option<f64>,
11    pub status: EstimateStatus,
12    pub due_date: Option<NaiveDate>,
13    pub completed_at: Option<DateTime<Utc>>,
14    pub created_at: DateTime<Utc>,
15    pub updated_at: DateTime<Utc>,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum EstimateStatus {
20    Planned,
21    InProgress,
22    Completed,
23    Cancelled,
24}
25
26impl std::fmt::Display for EstimateStatus {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            EstimateStatus::Planned => write!(f, "planned"),
30            EstimateStatus::InProgress => write!(f, "in_progress"),
31            EstimateStatus::Completed => write!(f, "completed"),
32            EstimateStatus::Cancelled => write!(f, "cancelled"),
33        }
34    }
35}
36
37impl TimeEstimate {
38    pub fn new(project_id: i64, task_name: String, estimated_hours: f64) -> Self {
39        let now = Utc::now();
40        Self {
41            id: None,
42            project_id,
43            task_name,
44            estimated_hours,
45            actual_hours: None,
46            status: EstimateStatus::Planned,
47            due_date: None,
48            completed_at: None,
49            created_at: now,
50            updated_at: now,
51        }
52    }
53
54    pub fn with_due_date(mut self, due_date: Option<NaiveDate>) -> Self {
55        self.due_date = due_date;
56        self
57    }
58
59    pub fn record_actual(&mut self, hours: f64) {
60        self.actual_hours = Some(hours);
61        self.updated_at = Utc::now();
62
63        if self.status == EstimateStatus::InProgress {
64            self.status = EstimateStatus::Completed;
65            self.completed_at = Some(Utc::now());
66        }
67    }
68
69    pub fn variance(&self) -> Option<f64> {
70        self.actual_hours
71            .map(|actual| actual - self.estimated_hours)
72    }
73
74    pub fn variance_percentage(&self) -> Option<f64> {
75        self.variance().map(|v| (v / self.estimated_hours) * 100.0)
76    }
77
78    pub fn is_over_estimate(&self) -> bool {
79        self.variance().map(|v| v > 0.0).unwrap_or(false)
80    }
81
82    pub fn is_under_estimate(&self) -> bool {
83        self.variance().map(|v| v < 0.0).unwrap_or(false)
84    }
85}