use crate::models::goal::{Budget, Goal};
use crate::models::time_grid;
use crate::technical::error::SchedulerError;
pub const SCHEMA_VERSION_LEGACY_HOURS: u32 = 1;
pub const SCHEMA_VERSION_MINUTES: u32 = 2;
fn to_minutes(
value: usize,
schema_version: u32,
field: &'static str,
) -> Result<usize, SchedulerError> {
let minutes = match schema_version {
SCHEMA_VERSION_LEGACY_HOURS => value
.checked_mul(60)
.ok_or(SchedulerError::ValueOutOfRange { field, value })?,
SCHEMA_VERSION_MINUTES => value,
other => {
return Err(SchedulerError::UnsupportedSchemaVersion { version: other });
}
};
if minutes % time_grid::SLOT_MINUTES as usize != 0 {
return Err(SchedulerError::NotSlotAligned {
field,
value: minutes,
slot_minutes: time_grid::SLOT_MINUTES as usize,
});
}
Ok(minutes)
}
fn minutes_to_slots(minutes: usize, field: &'static str) -> Result<usize, SchedulerError> {
time_grid::minutes_to_slots(minutes).ok_or(SchedulerError::NotSlotAligned {
field,
value: minutes,
slot_minutes: time_grid::SLOT_MINUTES as usize,
})
}
pub fn normalize_goals(goals: &mut [Goal], schema_version: u32) -> Result<(), SchedulerError> {
for goal in goals.iter_mut() {
if let Some(min_duration) = &mut goal.min_duration {
let minutes = to_minutes(*min_duration, schema_version, "goal.minDuration")?;
*min_duration = minutes_to_slots(minutes, "goal.minDuration")?;
}
}
Ok(())
}
pub fn normalize_budgets(
budgets: &mut [Budget],
schema_version: u32,
) -> Result<(), SchedulerError> {
for budget in budgets.iter_mut() {
budget.min_per_week = minutes_to_slots(
to_minutes(budget.min_per_week, schema_version, "budget.minPerWeek")?,
"budget.minPerWeek",
)?;
budget.max_per_week = minutes_to_slots(
to_minutes(budget.max_per_week, schema_version, "budget.maxPerWeek")?,
"budget.maxPerWeek",
)?;
for period in budget.periods.iter_mut() {
period.min_for_period = minutes_to_slots(
to_minutes(period.min_for_period, schema_version, "period.minForPeriod")?,
"period.minForPeriod",
)?;
period.max_for_period = minutes_to_slots(
to_minutes(period.max_for_period, schema_version, "period.maxForPeriod")?,
"period.maxForPeriod",
)?;
let after_minutes = to_minutes(
period.window.after_time,
schema_version,
"period.window.afterTime",
)?;
let before_minutes = to_minutes(
period.window.before_time,
schema_version,
"period.window.beforeTime",
)?;
period.window.after_time = minutes_to_slots(after_minutes, "period.window.afterTime")?;
period.window.before_time =
minutes_to_slots(before_minutes, "period.window.beforeTime")?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::goal::{Budget, BudgetPeriod, Goal, PeriodWindow};
use chrono::NaiveDate;
use chrono::Weekday;
fn goal_with_duration(min_duration: usize) -> Goal {
Goal {
id: "g1".into(),
start: NaiveDate::from_ymd_opt(2022, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
deadline: None,
min_duration: Some(min_duration),
title: "test".into(),
budget_id: None,
not_on: None,
}
}
#[test]
fn legacy_hours_convert_to_slots() {
let mut goals = vec![goal_with_duration(1)];
normalize_goals(&mut goals, SCHEMA_VERSION_LEGACY_HOURS).unwrap();
assert_eq!(goals[0].min_duration, Some(12)); }
#[test]
fn minutes_schema_converts_to_slots() {
let mut goals = vec![goal_with_duration(25)];
normalize_goals(&mut goals, SCHEMA_VERSION_MINUTES).unwrap();
assert_eq!(goals[0].min_duration, Some(5)); }
#[test]
fn rejects_non_aligned_minutes() {
let mut goals = vec![goal_with_duration(7)];
let err = normalize_goals(&mut goals, SCHEMA_VERSION_MINUTES).unwrap_err();
assert!(matches!(err, SchedulerError::NotSlotAligned { .. }));
}
#[test]
fn period_window_minutes_convert_to_slots() {
let mut budgets = vec![Budget {
id: "b1".into(),
title: None,
min_per_week: 60,
max_per_week: 120,
periods: vec![BudgetPeriod {
id: None,
on_days: vec![Weekday::Mon],
window: PeriodWindow {
after_time: 1095, before_time: 1170, },
min_for_period: 30,
max_for_period: 60,
}],
}];
normalize_budgets(&mut budgets, SCHEMA_VERSION_MINUTES).unwrap();
assert_eq!(budgets[0].periods[0].window.after_time, 219);
assert_eq!(budgets[0].periods[0].window.before_time, 234);
assert_eq!(budgets[0].periods[0].min_for_period, 6);
assert_eq!(budgets[0].min_per_week, 12);
}
}