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); 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); }
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); }
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); }
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); }
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); }
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 record.end = TimePoint::new(11, 30).unwrap();
140 record.update_duration();
141 assert_eq!(record.total_minutes, 150); }
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}