Skip to main content

work_tuimer/models/
work_record.rs

1use super::TimePoint;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct WorkRecord {
6    pub id: u32,
7    pub name: String,
8    pub start: TimePoint,
9    pub end: TimePoint,
10    pub total_minutes: u32,
11    #[serde(default)]
12    pub project: String,
13    #[serde(default)]
14    pub customer: String,
15    #[serde(default)]
16    pub description: String,
17}
18
19impl WorkRecord {
20    pub fn new(id: u32, name: String, start: TimePoint, end: TimePoint) -> Self {
21        let total_minutes = Self::calculate_duration(&start, &end);
22        WorkRecord {
23            id,
24            name,
25            start,
26            end,
27            total_minutes,
28            project: String::new(),
29            customer: String::new(),
30            description: String::new(),
31        }
32    }
33
34    pub fn calculate_duration(start: &TimePoint, end: &TimePoint) -> u32 {
35        let start_mins = start.to_minutes_since_midnight();
36        let end_mins = end.to_minutes_since_midnight();
37
38        if end_mins >= start_mins {
39            end_mins - start_mins
40        } else {
41            (24 * 60 - start_mins) + end_mins
42        }
43    }
44
45    pub fn update_duration(&mut self) {
46        self.total_minutes = Self::calculate_duration(&self.start, &self.end);
47    }
48
49    pub fn format_duration(&self) -> String {
50        let hours = self.total_minutes / 60;
51        let minutes = self.total_minutes % 60;
52        format!("{}h {:02}m", hours, minutes)
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_new_work_record() {
62        let start = TimePoint::new(9, 0).unwrap();
63        let end = TimePoint::new(17, 0).unwrap();
64        let record = WorkRecord::new(1, "Coding".to_string(), start, end);
65
66        assert_eq!(record.id, 1);
67        assert_eq!(record.name, "Coding");
68        assert_eq!(record.start, start);
69        assert_eq!(record.end, end);
70        assert_eq!(record.total_minutes, 480); // 8 hours
71        assert_eq!(record.project, "");
72        assert_eq!(record.customer, "");
73        assert_eq!(record.description, "");
74    }
75
76    #[test]
77    fn test_calculate_duration_same_day() {
78        let start = TimePoint::new(9, 0).unwrap();
79        let end = TimePoint::new(17, 30).unwrap();
80        let duration = WorkRecord::calculate_duration(&start, &end);
81        assert_eq!(duration, 510); // 8h 30m = 510 minutes
82    }
83
84    #[test]
85    fn test_calculate_duration_zero() {
86        let time = TimePoint::new(12, 0).unwrap();
87        let duration = WorkRecord::calculate_duration(&time, &time);
88        assert_eq!(duration, 0);
89    }
90
91    #[test]
92    fn test_calculate_duration_one_minute() {
93        let start = TimePoint::new(12, 0).unwrap();
94        let end = TimePoint::new(12, 1).unwrap();
95        let duration = WorkRecord::calculate_duration(&start, &end);
96        assert_eq!(duration, 1);
97    }
98
99    #[test]
100    fn test_calculate_duration_overnight() {
101        let start = TimePoint::new(23, 0).unwrap();
102        let end = TimePoint::new(1, 0).unwrap();
103        let duration = WorkRecord::calculate_duration(&start, &end);
104        assert_eq!(duration, 120); // 2 hours
105    }
106
107    #[test]
108    fn test_calculate_duration_overnight_complex() {
109        let start = TimePoint::new(22, 30).unwrap();
110        let end = TimePoint::new(2, 15).unwrap();
111        let duration = WorkRecord::calculate_duration(&start, &end);
112        assert_eq!(duration, 225); // 3h 45m = 225 minutes
113    }
114
115    #[test]
116    fn test_calculate_duration_full_day() {
117        let start = TimePoint::new(0, 0).unwrap();
118        let end = TimePoint::new(0, 0).unwrap();
119        let duration = WorkRecord::calculate_duration(&start, &end);
120        assert_eq!(duration, 0); // Same time = 0 duration
121    }
122
123    #[test]
124    fn test_calculate_duration_almost_full_day() {
125        let start = TimePoint::new(0, 1).unwrap();
126        let end = TimePoint::new(0, 0).unwrap();
127        let duration = WorkRecord::calculate_duration(&start, &end);
128        assert_eq!(duration, 1439); // 23h 59m
129    }
130
131    #[test]
132    fn test_update_duration() {
133        let start = TimePoint::new(9, 0).unwrap();
134        let end = TimePoint::new(10, 0).unwrap();
135        let mut record = WorkRecord::new(1, "Task".to_string(), start, end);
136        assert_eq!(record.total_minutes, 60);
137
138        // Change the end time
139        record.end = TimePoint::new(11, 30).unwrap();
140        record.update_duration();
141        assert_eq!(record.total_minutes, 150); // 2h 30m
142    }
143
144    #[test]
145    fn test_format_duration_zero() {
146        let start = TimePoint::new(9, 0).unwrap();
147        let end = TimePoint::new(9, 0).unwrap();
148        let record = WorkRecord::new(1, "Task".to_string(), start, end);
149        assert_eq!(record.format_duration(), "0h 00m");
150    }
151
152    #[test]
153    fn test_format_duration_minutes_only() {
154        let start = TimePoint::new(9, 0).unwrap();
155        let end = TimePoint::new(9, 45).unwrap();
156        let record = WorkRecord::new(1, "Task".to_string(), start, end);
157        assert_eq!(record.format_duration(), "0h 45m");
158    }
159
160    #[test]
161    fn test_format_duration_hours_only() {
162        let start = TimePoint::new(9, 0).unwrap();
163        let end = TimePoint::new(12, 0).unwrap();
164        let record = WorkRecord::new(1, "Task".to_string(), start, end);
165        assert_eq!(record.format_duration(), "3h 00m");
166    }
167
168    #[test]
169    fn test_format_duration_hours_and_minutes() {
170        let start = TimePoint::new(9, 15).unwrap();
171        let end = TimePoint::new(17, 45).unwrap();
172        let record = WorkRecord::new(1, "Task".to_string(), start, end);
173        assert_eq!(record.format_duration(), "8h 30m");
174    }
175
176    #[test]
177    fn test_format_duration_long() {
178        let start = TimePoint::new(0, 0).unwrap();
179        let end = TimePoint::new(23, 59).unwrap();
180        let record = WorkRecord::new(1, "Task".to_string(), start, end);
181        assert_eq!(record.format_duration(), "23h 59m");
182    }
183
184    #[test]
185    fn test_description_field() {
186        let start = TimePoint::new(9, 0).unwrap();
187        let end = TimePoint::new(10, 0).unwrap();
188        let mut record = WorkRecord::new(1, "Task".to_string(), start, end);
189
190        assert_eq!(record.description, "");
191        record.description = "Important meeting notes".to_string();
192        assert_eq!(record.description, "Important meeting notes");
193    }
194
195    #[test]
196    fn test_project_and_customer_fields() {
197        let start = TimePoint::new(9, 0).unwrap();
198        let end = TimePoint::new(10, 0).unwrap();
199        let mut record = WorkRecord::new(1, "Task".to_string(), start, end);
200
201        assert_eq!(record.project, "");
202        assert_eq!(record.customer, "");
203
204        record.project = "Internal Platform".to_string();
205        record.customer = "ACME".to_string();
206
207        assert_eq!(record.project, "Internal Platform");
208        assert_eq!(record.customer, "ACME");
209    }
210
211    #[test]
212    fn test_clone() {
213        let start = TimePoint::new(9, 0).unwrap();
214        let end = TimePoint::new(17, 0).unwrap();
215        let record1 = WorkRecord::new(1, "Coding".to_string(), start, end);
216        let record2 = record1.clone();
217
218        assert_eq!(record1.id, record2.id);
219        assert_eq!(record1.name, record2.name);
220        assert_eq!(record1.start, record2.start);
221        assert_eq!(record1.end, record2.end);
222        assert_eq!(record1.total_minutes, record2.total_minutes);
223        assert_eq!(record1.project, record2.project);
224        assert_eq!(record1.customer, record2.customer);
225    }
226}