use csv::Reader;
use serde::{Deserialize, Serialize};
use crate::{
args::Args,
formaters::{format_money, format_time},
parse::parse_month,
Config,
};
use colored::Colorize;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Shift {
pub date: String,
pub time: f32,
pub premium: f32,
}
impl Shift {
fn format(&self) -> String {
if self.premium > 0. {
return format!(
" Date: {}\n Time: {}\n Premium: {}%,\n",
self.date.yellow(),
format_time(self.time).yellow(),
(self.premium * 100.).to_string().green()
);
}
format!(
" Date: {}\n Time: {},\n",
self.date.yellow(),
format_time(self.time).yellow()
)
}
pub fn format_and_calc(&self, config: Config) -> String {
let mut premium = 1.;
let mut premium_str = "".to_string();
if self.premium > 0. {
premium_str = format!("\nPremium: {}%", (self.premium * 100.).to_string().green());
premium = self.premium;
}
let income = (self.time * (config.rate * premium)) - config.cost_per_shift;
let mut after_tax = "".to_string();
if config.tax_percent > 0. {
after_tax = format!(
"\nIncome after tax: {:2}{}",
income * config.tax_percent,
config.currency
);
}
format!(
"Date: {}\nTime: {}{}\nIncome: {}{}{}",
self.date.to_string().yellow(),
format_time(self.time).yellow(),
premium_str,
income.to_string().green(),
config.currency.green(),
after_tax.green()
)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Shifts {
pub shifts: Vec<Shift>,
}
impl Shifts {
pub fn from_file(db_path: String) -> Self {
let mut rdr = match Reader::from_path(db_path) {
Ok(a) => a,
Err(_) => {
return Self { shifts: Vec::new() };
}
};
let db = rdr
.deserialize()
.map(|v| {
let shift: Shift = v.unwrap();
shift
})
.collect::<Vec<Shift>>();
Shifts { shifts: db }
}
pub fn format(&self, config: Config) -> String {
let mut money_sum = 0.;
let mut hours_sum: f32 = 0.;
let shifts_formatted = self
.shifts
.iter()
.map(|v| {
hours_sum += v.time;
let rate;
if v.premium == 0. {
rate = config.rate;
} else {
rate = config.rate * v.premium;
}
money_sum += (v.time * rate) - config.cost_per_shift as f32;
v.format()
})
.collect::<Vec<String>>()
.join("\n");
let hours_sum_formatted = format_time(hours_sum).yellow();
if config.tax_percent > 0.0 {
let money_sum_after_tax = money_sum - (money_sum * config.tax_percent);
return format!(
"Shifts: {{\n{}}}\n\nTotal shifts:\n{}\n\nTotal hours:\n{}\n\nTotal Income:\n{}\n\nTotal Income after Tax:\n{}",
shifts_formatted,
self.shifts.len(),
hours_sum_formatted,
format_money(money_sum, &config.currency),
format_money(money_sum_after_tax, &config.currency)
);
} else {
return format!(
"Shifts: {{\n{}}}\n\nTotal shifts:\n{}\n\nTotal hours:\n{}\n\nTotal Income:\n{}",
shifts_formatted,
self.shifts.len().to_string().yellow(),
hours_sum_formatted.to_string().yellow(),
format_money(money_sum, &config.currency),
);
}
}
pub fn stats(&self, config: Config, args: Args) -> String {
let month_string = parse_month(args.date).unwrap();
let date_split = month_string.split('/').collect::<Vec<&str>>();
let (this_month, this_year) = (
date_split[0].parse::<i32>().unwrap(),
date_split[1].parse::<i32>().unwrap(),
);
let mut shift_hours_sum = 0.;
let mut salaries = vec![Salary {
date: None,
hours: 0.,
income: 0.,
}];
let mut month_idx = 0;
let mut last_month = -1;
for (idx, shift) in self.shifts.iter().enumerate() {
let date_split = shift.date.split('/').collect::<Vec<&str>>();
let (month, year) = (
date_split[1].parse::<i32>().unwrap(),
date_split[2].parse::<i32>().unwrap(),
);
if idx == 0 {
last_month = month;
}
if year < this_year || month < this_month && year == this_year {
if last_month != month {
month_idx += 1;
let money = shift.time * config.rate - config.cost_per_shift;
salaries.push(Salary {
date: Some(format!("{}/{}", month, year)),
hours: shift.time,
income: money,
});
last_month = month;
} else {
if salaries[month_idx].date.is_none() {
salaries[month_idx].date = Some(format!("{}/{}", month, year));
}
salaries[month_idx].hours += shift.time;
salaries[month_idx].income += shift.time * config.rate - config.cost_per_shift;
}
}
shift_hours_sum += shift.time;
}
let shift_hours_avg = shift_hours_sum / self.shifts.len() as f32;
let shift_money_avg = shift_hours_avg * config.rate - config.cost_per_shift;
let shift_money_after_tax_avg = shift_money_avg - (shift_money_avg * config.tax_percent);
let avg_salary = avrage_slary(salaries.clone());
let salaries_formatted = salaries
.iter()
.map(|v| format!("\n{},", v.format(config.clone())))
.collect::<Vec<String>>();
let mut shift_money_after_tax_avg_formatted = "".to_string();
if config.tax_percent > 0. {
shift_money_after_tax_avg_formatted = format!(
"\n Income after tax: {}",
format_money(shift_money_after_tax_avg, &config.currency)
);
}
return format!(
"Months: {{{}\n}}\n\nAvrage Salary:\n{}\n\nAvrage shift:\n Time: {}\n Income: {}{}",
salaries_formatted.join("\n"),
avg_salary.format(config.clone()),
format_time(shift_hours_avg).yellow(),
format_money(shift_money_avg, &config.currency),
shift_money_after_tax_avg_formatted,
);
}
}
fn avrage_slary(salaries: Vec<Salary>) -> Salary {
let mut sum_salary = Salary {
date: None,
hours: 0.,
income: 0.,
};
for sal in &salaries {
sum_salary.hours += sal.hours;
sum_salary.income += sal.income;
}
Salary {
date: None,
hours: sum_salary.hours / salaries.len() as f32,
income: sum_salary.income / salaries.len() as f32,
}
}
#[derive(Debug, Clone)]
struct Salary {
date: Option<String>,
hours: f32,
income: f32,
}
impl Salary {
fn format(&self, config: Config) -> String {
let date = match &self.date {
Some(date) => format!(" Month: {}\n", date.yellow()),
None => "".to_string(),
};
let mut income_after_tax = "".to_string();
if config.tax_percent > 0. {
income_after_tax = format!(
"\n Total Income after tax: {}",
format_money(
self.income - (self.income * config.tax_percent),
&config.currency
)
)
}
return format!(
"{} Total Hours: {}\n Total Income: {}{}",
date,
format_time(self.hours),
format_money(self.income, &config.currency),
income_after_tax
);
}
}