chore: switch from h-float to min-int in timesheet
This commit is contained in:
parent
a79111c650
commit
d614d678af
3 changed files with 156 additions and 135 deletions
|
|
@ -48,7 +48,7 @@ pub enum DayWarning {
|
|||
second: (NaiveTime, NaiveTime),
|
||||
},
|
||||
/// Work logged outside any configured period.
|
||||
OutsidePeriod { hours_worked: f64 },
|
||||
OutsidePeriod { minutes_worked: i64 },
|
||||
}
|
||||
|
||||
impl fmt::Display for DayWarning {
|
||||
|
|
@ -67,8 +67,12 @@ impl fmt::Display for DayWarning {
|
|||
second.1.format("%H:%M")
|
||||
)
|
||||
}
|
||||
DayWarning::OutsidePeriod { hours_worked } => {
|
||||
write!(f, "{:.1}h worked (no period configured)", hours_worked)
|
||||
DayWarning::OutsidePeriod { minutes_worked } => {
|
||||
write!(
|
||||
f,
|
||||
"{:.1}h worked (no period configured)",
|
||||
*minutes_worked as f64 / 60.0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,18 +82,23 @@ impl fmt::Display for DayWarning {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct DayReport {
|
||||
pub date: NaiveDate,
|
||||
pub expected_hours: f64,
|
||||
pub actual_hours: f64,
|
||||
pub expected_minutes: i64,
|
||||
pub actual_minutes: i64,
|
||||
pub day_type: DayType,
|
||||
pub warnings: Vec<DayWarning>,
|
||||
}
|
||||
|
||||
impl DayReport {
|
||||
pub fn new(date: NaiveDate, expected_hours: f64, actual_hours: f64, day_type: DayType) -> Self {
|
||||
pub fn new(
|
||||
date: NaiveDate,
|
||||
expected_minutes: i64,
|
||||
actual_minutes: i64,
|
||||
day_type: DayType,
|
||||
) -> Self {
|
||||
Self {
|
||||
date,
|
||||
expected_hours,
|
||||
actual_hours,
|
||||
expected_minutes,
|
||||
actual_minutes,
|
||||
day_type,
|
||||
warnings: Vec::new(),
|
||||
}
|
||||
|
|
@ -105,9 +114,9 @@ impl DayReport {
|
|||
self
|
||||
}
|
||||
|
||||
/// Calculate the difference between actual and expected hours.
|
||||
pub fn diff(&self) -> f64 {
|
||||
self.actual_hours - self.expected_hours
|
||||
/// Calculate the difference between actual and expected minutes.
|
||||
pub fn diff(&self) -> i64 {
|
||||
self.actual_minutes - self.expected_minutes
|
||||
}
|
||||
|
||||
/// Check if this day has any warnings.
|
||||
|
|
@ -138,18 +147,18 @@ impl MonthReport {
|
|||
self
|
||||
}
|
||||
|
||||
/// Calculate total expected hours for the month.
|
||||
pub fn total_expected(&self) -> f64 {
|
||||
self.days.iter().map(|d| d.expected_hours).sum()
|
||||
/// Calculate total expected minutes for the month.
|
||||
pub fn total_expected(&self) -> i64 {
|
||||
self.days.iter().map(|d| d.expected_minutes).sum()
|
||||
}
|
||||
|
||||
/// Calculate total actual hours for the month.
|
||||
pub fn total_actual(&self) -> f64 {
|
||||
self.days.iter().map(|d| d.actual_hours).sum()
|
||||
/// Calculate total actual minutes for the month.
|
||||
pub fn total_actual(&self) -> i64 {
|
||||
self.days.iter().map(|d| d.actual_minutes).sum()
|
||||
}
|
||||
|
||||
/// Calculate the difference for the month.
|
||||
pub fn diff(&self) -> f64 {
|
||||
pub fn diff(&self) -> i64 {
|
||||
self.total_actual() - self.total_expected()
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +187,7 @@ impl ReportWarning {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct TimesheetReport {
|
||||
pub months: Vec<MonthReport>,
|
||||
pub cumulative_balance: f64,
|
||||
pub cumulative_balance: i64,
|
||||
pub warnings: Vec<ReportWarning>,
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +195,7 @@ impl TimesheetReport {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
months: Vec::new(),
|
||||
cumulative_balance: 0.0,
|
||||
cumulative_balance: 0,
|
||||
warnings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +205,7 @@ impl TimesheetReport {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_cumulative_balance(mut self, balance: f64) -> Self {
|
||||
pub fn with_cumulative_balance(mut self, balance: i64) -> Self {
|
||||
self.cumulative_balance = balance;
|
||||
self
|
||||
}
|
||||
|
|
@ -232,27 +241,30 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_day_report_diff() {
|
||||
let report = DayReport::new(date(2026, 3, 2), 7.6, 8.2, DayType::Regular);
|
||||
assert!((report.diff() - 0.6).abs() < 0.0001);
|
||||
// 7.6h = 456 min, 8.2h = 492 min, diff = 36 min
|
||||
let report = DayReport::new(date(2026, 3, 2), 456, 492, DayType::Regular);
|
||||
assert_eq!(report.diff(), 36);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_report_negative_diff() {
|
||||
let report = DayReport::new(date(2026, 3, 2), 7.6, 6.0, DayType::Regular);
|
||||
assert!((report.diff() - (-1.6)).abs() < 0.0001);
|
||||
// 7.6h = 456 min, 6.0h = 360 min, diff = -96 min
|
||||
let report = DayReport::new(date(2026, 3, 2), 456, 360, DayType::Regular);
|
||||
assert_eq!(report.diff(), -96);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_month_report_totals() {
|
||||
// 7.6h = 456 min, 8.2h = 492 min, 6.0h = 360 min
|
||||
let month = MonthReport::new(2026, 3).with_days(vec![
|
||||
DayReport::new(date(2026, 3, 2), 7.6, 8.2, DayType::Regular),
|
||||
DayReport::new(date(2026, 3, 3), 7.6, 7.6, DayType::Regular),
|
||||
DayReport::new(date(2026, 3, 4), 7.6, 6.0, DayType::Regular),
|
||||
DayReport::new(date(2026, 3, 2), 456, 492, DayType::Regular),
|
||||
DayReport::new(date(2026, 3, 3), 456, 456, DayType::Regular),
|
||||
DayReport::new(date(2026, 3, 4), 456, 360, DayType::Regular),
|
||||
]);
|
||||
|
||||
assert!((month.total_expected() - 22.8).abs() < 0.0001);
|
||||
assert!((month.total_actual() - 21.8).abs() < 0.0001);
|
||||
assert!((month.diff() - (-1.0)).abs() < 0.0001);
|
||||
assert_eq!(month.total_expected(), 1368); // 456 * 3
|
||||
assert_eq!(month.total_actual(), 1308); // 492 + 456 + 360
|
||||
assert_eq!(month.diff(), -60); // -1 hour
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -281,13 +293,15 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_day_warning_outside_period_display() {
|
||||
let warning = DayWarning::OutsidePeriod { hours_worked: 3.5 };
|
||||
let warning = DayWarning::OutsidePeriod {
|
||||
minutes_worked: 210,
|
||||
}; // 3.5h
|
||||
assert_eq!(warning.to_string(), "3.5h worked (no period configured)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_report_with_warnings() {
|
||||
let report = DayReport::new(date(2026, 3, 2), 7.6, 8.2, DayType::Regular).with_warning(
|
||||
let report = DayReport::new(date(2026, 3, 2), 456, 492, DayType::Regular).with_warning(
|
||||
DayWarning::OverlappingTimecards {
|
||||
first: (time(9, 0), time(12, 30)),
|
||||
second: (time(12, 0), time(13, 0)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue