Skip to main content

paycheck_utils/
interaction.rs

1//! This module handles all user interaction to gather the necessary information to create an employment scenario struct
2//! This includes functions for displaying prompts, receiving input, and showing results. The main function in this module is `get_user_input` which orchestrates the entire process of gathering information from the user and creating an employment scenario struct.
3//!
4//! The `get_user_input` function first prompts the user to create an employment scenario by calling the `create_scenario` function, which gathers information about the user's hourly rate and hours worked per week. Then it prompts the user to enter their living expenses by calling the `get_expenses` function, which gathers information about various expense categories. Finally, it prompts the user to enter their deductions by calling the `get_deductions` function, which gathers information about both pre-tax and post-tax deductions. After gathering all the necessary information, it confirms the inputs with the user and then converts the inputs into an employment scenario struct using the `convert_inputs_to_struct` function.
5//!
6//! The `create_scenario`, `get_expenses`, and `get_deductions` functions all follow a similar pattern of prompting the user for input, validating the input, and storing it in a HashMap. The `confirm_inputs` function is used to display the gathered information back to the user for confirmation before proceeding to create the employment scenario struct. The `convert_inputs_to_struct` function takes the gathered information from the HashMaps and constructs an `EmploymentScenario` struct with the appropriate fields populated based on the user's input.
7
8/// checks the converted value of the user input to ensure it can be parsed into the expected type (in this case, a float). If the conversion is successful, it returns true; otherwise, it returns false. This function is used in the input validation loops in the `create_scenario`, `get_expenses`, and `get_deductions` functions to ensure that the user enters valid numeric input for the various fields.
9use crate::utils::check_converted_value;
10use crate::{
11    EmploymentScenario, Expense, Expenses, PostTaxDeduction, PostTaxDeductions, PreTaxDeduction,
12    PreTaxDeductions,
13};
14use std::any::TypeId;
15use std::collections::HashMap;
16use std::io;
17use std::io::prelude::*;
18
19/// main function to orchestrate user input and create employment scenario struct
20/// This function will call the other functions in this module to gather information from the user and create an employment scenario struct based on that information. It will start by getting the payrate and hours worked per week.
21pub fn get_user_input() -> EmploymentScenario {
22    println!(
23        "\n{:^100}",
24        "--- Let's start by gathering some information. ---"
25    );
26
27    // get employment scenario input
28    println!("\nFirst, let's create an employment scenario.\n");
29    let scenario = create_scenario();
30
31    // get expenses input
32    println!(
33        "\n{:^100}",
34        "--- Great!, now let's create some expenses. ---"
35    );
36    let expenses = get_expenses();
37
38    // get deductions input
39    println!(
40        "\n{:^100}",
41        "--- Finally, let's create some deductions. ---"
42    );
43    let deductions = get_deductions();
44
45    // create employment scenario struct using the inputs received from the user
46    convert_inputs_to_struct(scenario, expenses, deductions)
47}
48
49/// create scenario input by prompting the user for their hourly rate and hours worked per week. The input is cleaned and validated to ensure it can be converted to a float before storing it in a HashMap. The keys of the HashMap are "Rate" and "Hours" and the values are the user input for those fields.
50fn create_scenario() -> HashMap<String, String> {
51    let mut inputs: HashMap<String, String> = HashMap::new();
52    let mut input = String::new();
53    let employed = ["Rate", "Hours"];
54
55    for value in employed {
56        print!("{value}: ");
57        io::stdout().flush().unwrap_or_default();
58        io::stdin().read_line(&mut input).unwrap_or_default();
59        loop {
60            if check_converted_value(&input.trim().parse::<f32>(), TypeId::of::<f32>()) {
61                break;
62            } else {
63                print!(
64                    "Please enter a valid number for {value} (examples: 25, 25.5, or 25.00) --> {value}: "
65                );
66                input.clear();
67                io::stdout().flush().unwrap_or_default();
68                io::stdin().read_line(&mut input).unwrap_or_default();
69            }
70        }
71        inputs
72            .entry(value.trim().to_string())
73            .or_insert(input.trim().to_string());
74        input.clear();
75    }
76
77    inputs
78}
79
80/// prompt user for expenses input and return a HashMap of the inputs. Cleans the input and validates that it can be converted to a float before storing it in the HashMap. The keys of the HashMap are the expense categories and the values are the amounts entered by the user.
81fn get_expenses() -> HashMap<String, String> {
82    let mut inputs: HashMap<String, String> = HashMap::new();
83    let mut input = String::new();
84    let expense_categories = [
85        "Housing",
86        "Energy",
87        "Water",
88        "Gas",
89        "Internet",
90        "Phone",
91        "Car Payment",
92        "Car Insurance",
93        "Car Gas",
94        "Groceries",
95    ];
96
97    println!("\nLiving expenses can vary so enter an estimated amount per month or 0.\n");
98
99    for exp in expense_categories {
100        print!("{exp}: ");
101        io::stdout().flush().unwrap_or_default();
102        io::stdin().read_line(&mut input).unwrap_or_default();
103        loop {
104            if check_converted_value(&input.trim().parse::<f32>(), TypeId::of::<f32>()) {
105                break;
106            } else {
107                print!(
108                    "Please enter a valid number for {exp} (examples: 25, 25.5, or 25.00) --> {exp}: "
109                );
110                input.clear();
111                io::stdout().flush().unwrap_or_default();
112                io::stdin().read_line(&mut input).unwrap_or_default();
113            }
114        }
115        inputs
116            .entry(exp.trim().to_string())
117            .or_insert(input.trim().to_string());
118        input.clear();
119    }
120
121    inputs
122}
123
124/// prompt user for deductions input and return a HashMap of the inputs. Cleans the input and validates that it can be converted to a float before storing it in the HashMap. The keys of the HashMap are the deduction categories and the values are the amounts entered by the user. This function handles both pre-tax and post-tax deductions, prompting the user separately for each type of deduction.
125fn get_deductions() -> HashMap<String, String> {
126    let mut inputs: HashMap<String, String> = HashMap::new();
127    let mut input = String::new();
128    let pretax_categories = [
129        "Medical",
130        "Dental",
131        "Vision",
132        "Traditional401K",
133        "HSA",
134        "FSA",
135    ];
136    let posttax_categories = [
137        "Roth401K",
138        "Voluntary Life",
139        "Voluntary ADD",
140        "Voluntary STD",
141        "Voluntary LTD",
142        "Wage Garnishment",
143    ];
144
145    println!(
146        "\nDeductions come in two flavors: pre-tax and post-tax.\nLet's start with the pre-tax deductions.\n"
147    );
148
149    for pre in pretax_categories {
150        print!("{pre}: ");
151        io::stdout().flush().unwrap_or_default();
152        io::stdin().read_line(&mut input).unwrap_or_default();
153        loop {
154            if check_converted_value(&input.trim().parse::<f32>(), TypeId::of::<f32>()) {
155                break;
156            } else {
157                print!(
158                    "Please enter a valid number for {pre} (examples: 25, 25.5, or 25.00) --> {pre}: "
159                );
160                input.clear();
161                io::stdout().flush().unwrap_or_default();
162                io::stdin().read_line(&mut input).unwrap_or_default();
163            }
164        }
165        inputs
166            .entry(pre.trim().to_string())
167            .or_insert(input.trim().to_string());
168        input.clear();
169    }
170
171    println!("\nOk, now the post-tax deductions.\n");
172
173    for post in posttax_categories {
174        print!("{post}: ");
175        io::stdout().flush().unwrap_or_default();
176        io::stdin().read_line(&mut input).unwrap_or_default();
177        loop {
178            if check_converted_value(&input.trim().parse::<f32>(), TypeId::of::<f32>()) {
179                break;
180            } else {
181                print!(
182                    "Please enter a valid number for {post} (examples: 25, 25.5, or 25.00) --> {post}: "
183                );
184                input.clear();
185                io::stdout().flush().unwrap_or_default();
186                io::stdin().read_line(&mut input).unwrap_or_default();
187            }
188        }
189        inputs
190            .entry(post.trim().to_string())
191            .or_insert(input.trim().to_string());
192        input.clear();
193    }
194
195    inputs
196}
197
198/// This function takes the three HashMaps containing the user input for the employment scenario, expenses, and deductions, and converts them into an `EmploymentScenario` struct. It parses the string values from the HashMaps into the appropriate types (e.g., f32) and constructs the `EmploymentScenario` struct with the corresponding fields populated based on the user's input.
199pub fn convert_inputs_to_struct(
200    sc: HashMap<String, String>,
201    ex: HashMap<String, String>,
202    de: HashMap<String, String>,
203) -> EmploymentScenario {
204    let mut scene = EmploymentScenario::default();
205    scene.hourly_rate = sc["Rate"].parse::<f32>().unwrap_or_default();
206    scene.hours_per_week = sc["Hours"].parse::<f32>().unwrap_or_default();
207    scene.expenses = Expenses::new(vec![
208        Expense::Housing(ex["Housing"].parse::<f32>().ok()),
209        Expense::Energy(ex["Energy"].parse::<f32>().ok()),
210        Expense::Water(ex["Water"].parse::<f32>().ok()),
211        Expense::Gas(ex["Gas"].parse::<f32>().ok()),
212        Expense::Internet(ex["Internet"].parse::<f32>().ok()),
213        Expense::Phone(ex["Phone"].parse::<f32>().ok()),
214        Expense::Vehicle(ex["Car Payment"].parse::<f32>().ok()),
215        Expense::VehicleInsurance(ex["Car Insurance"].parse::<f32>().ok()),
216        Expense::VehicleGas(ex["Car Gas"].parse::<f32>().ok()),
217        Expense::Groceries(ex["Groceries"].parse::<f32>().ok()),
218    ]);
219    scene.pretax_deductions = PreTaxDeductions::new(vec![
220        PreTaxDeduction::Medical(de["Medical"].parse::<f32>().ok()),
221        PreTaxDeduction::Dental(de["Dental"].parse::<f32>().ok()),
222        PreTaxDeduction::Vision(de["Vision"].parse::<f32>().ok()),
223        PreTaxDeduction::Traditional401K(de["Traditional401K"].parse::<f32>().ok()),
224        PreTaxDeduction::HSA(de["HSA"].parse::<f32>().ok()),
225        PreTaxDeduction::FSA(de["FSA"].parse::<f32>().ok()),
226    ]);
227    scene.posttax_deductions = PostTaxDeductions::new(vec![
228        PostTaxDeduction::Roth401K(de["Roth401K"].parse::<f32>().ok()),
229        PostTaxDeduction::VoluntaryLife(de["Voluntary Life"].parse::<f32>().ok()),
230        PostTaxDeduction::VoluntaryADD(de["Voluntary ADD"].parse::<f32>().ok()),
231        PostTaxDeduction::VoluntarySTD(de["Voluntary STD"].parse::<f32>().ok()),
232        PostTaxDeduction::VoluntaryLTD(de["Voluntary LTD"].parse::<f32>().ok()),
233        PostTaxDeduction::WageGarnishment(de["Wage Garnishment"].parse::<f32>().ok()),
234    ]);
235
236    scene
237}