chore: switch from h-float to min-int in timesheet
Some checks failed
Release / Build and Release (push) Successful in 6s
Continuous Integration / Lint, Check & Test (push) Failing after 56s
Continuous Integration / Build Package (push) Successful in 1m42s

This commit is contained in:
Konstantin Fickel 2026-04-07 08:28:50 +02:00
parent a79111c650
commit d614d678af
Signed by: kfickel
GPG key ID: A793722F9933C1A5
3 changed files with 156 additions and 135 deletions

View file

@ -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)),