Skip to content

Commit 2293ada

Browse files
committed
feat(sickleave): add Sick Leave marker day with optional --to range
- Introduce --pos s (Sick Leave) - Store Sick Leave as 00:00 marker event (non-working day) - Add optional range support via --to (start is DATE) - Update migrations to allow position 'S' - Update list rendering to hide times and exclude ΔWORK totals
1 parent 54c3d87 commit 2293ada

File tree

5 files changed

+50
-61
lines changed

5 files changed

+50
-61
lines changed

CHANGELOG.md

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,25 @@
44

55
### Added
66

7-
- New `Sick Leave` position (`--pos s`)
8-
- Support for optional date range with `--from` and `--to` (usable only with `--pos s`)
9-
- Marker-day logic for Sick Leave (stored as sentinel event at 00:00, similar to Holiday)
7+
- New position: `Sick Leave` (`--pos s`)
8+
- Optional Sick Leave range support via `--to` (start date is the command `DATE`)
109

1110
### Changed
1211

13-
- `Sick Leave` is now treated as a non-working marker day:
14-
- No IN/OUT times displayed in `list`
15-
- No contribution to daily or monthly ΔWORK totals
16-
- No target (TGT) calculation
17-
- Validation updated:
18-
- `--from/--to` allowed only with `--pos s`
19-
- If omitted, `--pos s` applies to the provided date only
20-
- Refactored `apply()` logic for cleaner separation between:
21-
- marker days (Holiday, NationalHoliday, SickLeave)
22-
- working days
23-
- `recalc_pairs_for_date` signature updated to accept `&Connection`
24-
(allows usage inside transactions)
12+
- Sick Leave is stored as a non-working marker day (sentinel event at `00:00`, similar to Holiday)
13+
- `list` rendering updated for Sick Leave marker days:
14+
- IN/OUT/TGT shown as `--:--`
15+
- ΔWORK does not contribute to daily/monthly totals
16+
- CLI validation updated:
17+
- `--to` is only allowed with `--pos s`
18+
- If `--to` is omitted, Sick Leave applies to the single `DATE`
19+
- Migration updated to support `S` position in `events.position` CHECK constraint
20+
- Improved `add` handler flow to support Sick Leave marker insertion and range insertion
2521

2622
### Fixed
2723

28-
- Prevented incorrect IN/OUT display (`00:00`) for Sick Leave in `list`
29-
- Prevented unintended ΔWORK surplus calculation for Sick Leave days
30-
- Improved argument validation consistency in `add` command
24+
- Prevented incorrect `00:00` IN time display for Sick Leave in `list`
25+
- Prevented Sick Leave days from producing surplus/deficit calculations
3126

3227
---
3328

src/cli/commands/add.rs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,30 @@ use chrono::NaiveDate;
99

1010
fn validate_sickleave_args(
1111
pos: Location,
12-
from: Option<NaiveDate>,
12+
date: Option<NaiveDate>, // di fatto: Some(d)
1313
to: Option<NaiveDate>,
1414
) -> Result<Option<(NaiveDate, NaiveDate)>, AppError> {
15-
match (from, to) {
16-
(Some(f), Some(t)) => {
17-
if pos != Location::SickLeave {
15+
let d = match date {
16+
Some(d) => d,
17+
None => return Ok(None), // difensivo, ma nel tuo handle passi sempre Some(d)
18+
};
19+
20+
match pos {
21+
Location::SickLeave => {
22+
let t = to.unwrap_or(d);
23+
if d > t {
24+
return Err(AppError::InvalidDateRange { from: d, to: t });
25+
}
26+
Ok(Some((d, t)))
27+
}
28+
_ => {
29+
if to.is_some() {
1830
return Err(AppError::InvalidArgs(
19-
"--from/--to can only be used with --pos s".into(),
31+
"--to can only be used with --pos s".into(),
2032
));
2133
}
22-
if f > t {
23-
return Err(AppError::InvalidDateRange { from: f, to: t });
24-
}
25-
Ok(Some((f, t)))
34+
Ok(None)
2635
}
27-
(None, None) => Ok(None),
28-
_ => Err(AppError::InvalidArgs(
29-
"Both --from and --to must be provided together.".into(),
30-
)),
3136
}
3237
}
3338

@@ -43,7 +48,6 @@ pub fn handle(cmd: &Commands, cfg: &crate::config::Config) -> AppResult<()> {
4348
end,
4449
edit_pair,
4550
edit,
46-
from,
4751
to,
4852
} = cmd
4953
{
@@ -100,32 +104,26 @@ pub fn handle(cmd: &Commands, cfg: &crate::config::Config) -> AppResult<()> {
100104
//
101105
// 7. SickLeave range validation (only if pos == SickLeave or from/to used)
102106
//
103-
let sick_range = validate_sickleave_args(pos_final, *from, *to)?;
107+
let sick_range = validate_sickleave_args(pos_final, Some(d), *to)?;
104108

105109
match sick_range {
106110
Some((from_date, to_date)) => {
107-
let default_in = chrono::NaiveTime::from_hms_opt(9, 0, 0).unwrap();
108-
let default_out = chrono::NaiveTime::from_hms_opt(18, 0, 0).unwrap();
109-
110111
// (opzionale ma consigliato) vieta start/end nel range malattia
111112
if start_parsed.is_some() || end_parsed.is_some() {
112113
return Err(AppError::InvalidArgs(
113-
"--start/--end cannot be used with --pos s (use only --from/--to)".into(),
114+
"--start/--end cannot be used with --pos s (use only --to)".into(),
114115
));
115116
}
116117

117-
let s = start_parsed.unwrap_or(default_in);
118-
let e = end_parsed.unwrap_or(default_out);
119-
120118
AddLogic::apply(
121119
cfg,
122120
&mut pool,
123121
d,
124122
pos_final,
125-
Some(s),
126-
lunch_opt,
127-
work_gap,
128-
Some(e),
123+
None,
124+
None,
125+
None,
126+
None,
129127
*edit,
130128
*edit_pair,
131129
Some(from_date),

src/cli/parser.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,6 @@ pub enum Commands {
127127
)]
128128
edit: bool,
129129

130-
/// Start date (YYYY-MM-DD). Only valid with --pos Malattia.
131-
#[arg(long, value_parser = parse_date)]
132-
from: Option<NaiveDate>,
133-
134130
/// End date (YYYY-MM-DD). Only valid with --pos Malattia.
135131
#[arg(long, value_parser = parse_date)]
136132
to: Option<NaiveDate>,

src/core/add.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ impl AddLogic {
7979
let pos_final = match &pos {
8080
Some(code) => Location::from_code(code).ok_or_else(|| {
8181
AppError::InvalidPosition(format!(
82-
"Invalid location code '{}'. Use a valid code such as 'O', 'R', 'H', 'N', 'C', 'M', 'S'.",
82+
"Invalid location code '{}'. Use a valid code such as 'O', 'R', 'H', 'N', 'C', 'M', 'S'.\n",
8383
code
8484
))
8585
})?,
@@ -193,7 +193,7 @@ impl AddLogic {
193193
None => ("✏️", "Pair updated"),
194194
};
195195

196-
success(format!("{} {} for pair {}.", icon, msg, pair_num));
196+
success(format!("{} {} for pair {}.\n", icon, msg, pair_num));
197197
return Ok(());
198198
}
199199

@@ -279,10 +279,10 @@ impl AddLogic {
279279
tx.commit()?;
280280

281281
if from_date == to_date {
282-
success(format!("Added SICK LEAVE on {}.", from_date));
282+
success(format!("Added SICK LEAVE on {}.\n", from_date));
283283
} else {
284284
success(format!(
285-
"Added SICK LEAVE from {} to {} ({} days).",
285+
"Added SICK LEAVE from {} to {} ({} days).\n",
286286
from_date,
287287
to_date,
288288
(to_date - from_date).num_days() + 1
@@ -336,8 +336,8 @@ impl AddLogic {
336336
recalc_pairs_for_date(&pool.conn, &date)?;
337337

338338
success(match pos_final {
339-
Location::Holiday => format!("Added HOLIDAY on {}.", date_str),
340-
Location::NationalHoliday => format!("Added NATIONAL HOLIDAY on {}.", date_str),
339+
Location::Holiday => format!("Added HOLIDAY on {}.\n", date_str),
340+
Location::NationalHoliday => format!("Added NATIONAL HOLIDAY on {}.\n", date_str),
341341
_ => unreachable!(),
342342
});
343343
return Ok(());
@@ -371,7 +371,7 @@ impl AddLogic {
371371
)?;
372372

373373
success(format!(
374-
"Lunch updated to {} minutes for {}.",
374+
"Lunch updated to {} minutes for {}.\n",
375375
lunch_val, date_str
376376
));
377377
return Ok(());
@@ -414,7 +414,7 @@ impl AddLogic {
414414
let tgt_str = crate::utils::time::format_minutes(tgt_mins);
415415

416416
success(format!(
417-
"Added IN at {} on {}. TGT => {}",
417+
"Added IN at {} on {}. TGT => {}\n",
418418
start_time, date_str, tgt_str
419419
));
420420
return Ok(());
@@ -468,7 +468,7 @@ impl AddLogic {
468468
recalc_pairs_for_date(&pool.conn, &date)?;
469469

470470
success(format!(
471-
"Added OUT on {} ({} → {}).",
471+
"Added OUT on {} ({} → {}).\n",
472472
date_str, last_in.time, end_time
473473
));
474474
return Ok(());
@@ -511,7 +511,7 @@ impl AddLogic {
511511
recalc_pairs_for_date(&pool.conn, &date)?;
512512

513513
success(format!(
514-
"Added IN/OUT pair on {}: {} → {}.",
514+
"Added IN/OUT pair on {}: {} → {}.\n",
515515
date_str, start_time, end_time
516516
));
517517
return Ok(());

src/errors.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ pub enum AppError {
4141
#[error("Invalid operation mode: {0}")]
4242
InvalidOperation(String),
4343

44-
#[error("Invalid date range: from ({from}) must be <= to ({to})")]
44+
#[error("Invalid date range: from ({from}) must be <= to ({to})\n")]
4545
InvalidDateRange { from: NaiveDate, to: NaiveDate },
4646

4747
// ---------------------------
4848
// Logic errors
4949
// ---------------------------
50-
#[error("Invalid arguments: {0}")]
50+
#[error("Invalid arguments: {0}\n")]
5151
InvalidArgs(String),
5252

5353
#[error("No events found for date {0}")]
@@ -86,7 +86,7 @@ pub enum AppError {
8686
// ---------------------------
8787
// Generic fallback
8888
// ---------------------------
89-
#[error("Internal error: {0}")]
89+
#[error("Internal error: {0}\nThis is likely a bug. Please report it to the developers.\n")]
9090
Other(String),
9191
}
9292

0 commit comments

Comments
 (0)