tempo_cli/models/
time_estimate.rs1use 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}