29_timesheet-management #64

Merged
kfickel merged 9 commits from 29_timesheet-management into main 2026-04-02 18:17:37 +02:00
2 changed files with 17 additions and 19 deletions
Showing only changes of commit d11a35c157 - Show all commits

View file

@ -5,6 +5,9 @@ use chrono::Datelike;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::config::Settings; use crate::config::Settings;
const SEPARATOR_WIDTH: usize = 71;
const COLUMN_SEPARATOR_WIDTH: usize = 65;
use crate::error::StreamdError; use crate::error::StreamdError;
use crate::extract::parse_markdown_file; use crate::extract::parse_markdown_file;
use crate::localize::localize_stream_file; use crate::localize::localize_stream_file;
@ -66,13 +69,10 @@ fn weekday_abbrev(date: chrono::NaiveDate) -> &'static str {
/// Print the timesheet report header. /// Print the timesheet report header.
fn print_header() { fn print_header() {
println!( let double_line = "\u{2550}".repeat(SEPARATOR_WIDTH);
"\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}" println!("{}", double_line);
);
println!(" TIMESHEET REPORT"); println!(" TIMESHEET REPORT");
println!( println!("{}", double_line);
"\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}"
);
println!(); println!();
} }
@ -91,10 +91,9 @@ fn print_month(month: &MonthReport) {
println!(); println!();
// Column headers // Column headers
let light_line = "\u{2500}".repeat(COLUMN_SEPARATOR_WIDTH);
println!(" Date Day Expected Actual Diff Type"); println!(" Date Day Expected Actual Diff Type");
println!( println!(" {}", light_line);
" \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}"
);
// Day rows // Day rows
for day in &month.days { for day in &month.days {
@ -132,9 +131,7 @@ fn print_month(month: &MonthReport) {
} }
// Monthly totals // Monthly totals
println!( println!(" {}", light_line);
" \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}"
);
println!( println!(
" Monthly: {:>7} {:>7} {:>6}", " Monthly: {:>7} {:>7} {:>6}",
format_hours(month.total_expected()), format_hours(month.total_expected()),
@ -146,16 +143,13 @@ fn print_month(month: &MonthReport) {
/// Print the cumulative balance. /// Print the cumulative balance.
fn print_cumulative_balance(balance: f64) { fn print_cumulative_balance(balance: f64) {
println!( let light_line = "\u{2500}".repeat(SEPARATOR_WIDTH);
"\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}" println!("{}", light_line);
);
println!( println!(
" CUMULATIVE BALANCE: {}", " CUMULATIVE BALANCE: {}",
format_diff(balance) format_diff(balance)
); );
println!( println!("{}", light_line);
"\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}"
);
} }
/// Print warnings section. /// Print warnings section.

View file

@ -136,8 +136,12 @@ fn aggregate_timecard_day(points: &[TimesheetPoint]) -> Result<Option<Timesheet>
fn aggregate_timecards(points: &[TimesheetPoint]) -> Result<Vec<Timesheet>, StreamdError> { fn aggregate_timecards(points: &[TimesheetPoint]) -> Result<Vec<Timesheet>, StreamdError> {
let mut timesheets = Vec::new(); let mut timesheets = Vec::new();
// Sort points by moment to ensure proper grouping
let mut sorted_points = points.to_vec();
sorted_points.sort_by_key(|p| p.moment);
// Group by date // Group by date
for (_date, group) in &points.iter().chunk_by(|p| p.moment.date_naive()) { for (_date, group) in &sorted_points.iter().chunk_by(|p| p.moment.date_naive()) {
let day_points: Vec<_> = group.cloned().collect(); let day_points: Vec<_> = group.cloned().collect();
if let Some(timesheet) = aggregate_timecard_day(&day_points)? { if let Some(timesheet) = aggregate_timecard_day(&day_points)? {
timesheets.push(timesheet); timesheets.push(timesheet);