diff --git a/data/DefaultCurrency.txt b/data/DefaultCurrency.txt index 822166bb78..00eacb6acb 100644 --- a/data/DefaultCurrency.txt +++ b/data/DefaultCurrency.txt @@ -1 +1 @@ -Default Currency: SGD \ No newline at end of file +Default Currency: USD \ No newline at end of file diff --git a/data/ExpenseFile.txt b/data/ExpenseFile.txt index e147d63ccd..e69de29bb2 100644 --- a/data/ExpenseFile.txt +++ b/data/ExpenseFile.txt @@ -1,16 +0,0 @@ -2024-04-07 | Transport | 100.00 | bus -2024-04-07 | Transport | 60.00 | train -2024-04-13 | Entertainment | 50.00 | Movie tickets -2024-04-13 | Housing | 1200.00 | Monthly rent -2024-04-13 | Groceries | 85.00 | Weekly grocery shopping -2024-04-13 | Transport | 30.00 | Bus pass -2024-04-13 | Entertainment | 75.00 | Concert ticket -2024-04-13 | Housing | 200.00 | Furniture -2024-04-13 | Groceries | 45.00 | Fresh fruits and vegetables -2024-04-13 | Transport | 70.00 | Taxi fare -2024-04-13 | Entertainment | 25.00 | Streaming service -2024-04-13 | Housing | 100.00 | Home repairs -2024-04-13 | Groceries | 110.00 | Meat and seafood -2024-04-13 | Transport | 40.00 | Petrol -2024-04-13 | Entertainment | 60.00 | Art exhibition ticket -2024-04-13 | Housing | 300.00 | Monthly parking space rental diff --git a/data/SavingsFile.txt b/data/SavingsFile.txt index e75a79eb50..e69de29bb2 100644 --- a/data/SavingsFile.txt +++ b/data/SavingsFile.txt @@ -1,7 +0,0 @@ -Salary | 1500.00 -Investments | 500.00 -Gifts | 200.00 -Salary | 1000.00 -Investments | 2000.00 -Gifts | 250.00 -Salary | 400.00 diff --git a/data/SplitExpensesFile.txt b/data/SplitExpensesFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1f849f154c..095ba79bf2 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -326,6 +326,95 @@ currencies within the budget management application. ## 4. Implementation +### Add Expense Feature + +The Add Expense Feature allows users to add expenses to different categories. `AddExpenseCommand` class enables this feature, +after initialized by the `Parser` class. Within the `AddExpense` object, the `Parser` would have initialized it with +4 variables, an `ExpenseList` object, along with a `category`, `amount` , `description`. +The relevance of these Class Attributes in `AddExpenseCommand` is as follows : + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|---------------------------------------------------| +| expenses | ExpenseList | ExpenseList Object containing the list of expenses| +| category | String | The category that the `expense` belongs to | +| amount | String | The amount spent | +| description | String | The description of the expense | + + +Upon the call of the `execute()` method in `BudgetBuddy` using `command.execute()`, +the `AddExpenseCommand` Object utilizes the following method from the `ExpenseList` class to add it to the existing +list of `expenses` matching against the corresponding `category`. + +| Method | Return Type | Relevance | +|--------------|-------------|-------------------------------------------------| +| addExpense() | void | Add expense to the existing list of `expenses` | + +The following UML Sequence diagram shows how the Parser works to obtain the relevant inputs for the Add Expense Feature : +![Sequence Diagram for Parser for Add Expense Feature](docs\diagram\sequenceDiagram_AddExpense.png) + +The following is a step-by-step explanation for the Parser for the Find Feature : +1. `BudgetBuddy` calls `Parser#parseCommand(input)` with `input` being the entire user input. +E.g `add expense c/Transport a/20 d/EZ-Link Top Up` +2. Within the `Parser`, it will have determined that the `input` is a Find Command from the `isAddExpenseCommand(input)` +function. +3. The `Parser` then self calls the method `handleAddExpenseCommand(input)` with the `input` still being the entire +user input. +4. Within `AddExpenseCommand(input)`, the first check would be the check for the existence of any combination of +`c/ , a/ and d/`. If none of these combinations were found, it immediately returns `null`. +5. If the checks in `4.` is passed, Three variables would be initialized. + + * | Variable Name | Variable Type | + |---------------|---------------| + | category | String | + | amount | String | + | description | String | +6. Depending on which parameters were present, the corresponding input would be extracted and placed into each variable +using the `Parser#extractDetailsForAdd(input, "parameter")` +7. Finally, `Parser#handleAddExpenseCommand()` returns a `AddExpensesCommand` to `Parser#parseCommand()`, which is +then returned to `BudgetBuddy` + +### Add Savings Feature + +The Add Savings Feature allows users to add savings to different categories. `AddSavingCommandCreator` class intialises the `AddSavingCommand`, after initialised by the `Parser` class. Within the `AddSavings` object, the `Parser` would have initialized it with +4 variables, a `SavingList` object, along with a `category`, `amount`. +The relevance of these Class Attributes in `AddExpenseCommand` is as follows : + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|---------------------------------------------------| +| savings | SavingList | SavingList Object containing the list of savings | +| category | String | The category that the `expense` belongs to | +| amount | String | The amount spent | + +Upon the call of the `execute()` method in `BudgetBuddy` using `command.execute()`, +the `AddSavingCommand` Object utilizes the following method from the `SavingList` class to add it to the existing +list of `savings` matching against the corresponding `category`. + +| Method | Return Type | Relevance | +|--------------|-------------|-------------------------------------------------| +| addSaving() | void | Add savings to the existing list of `savings` | + +The following UML Sequence diagram shows how the Parser works to obtain the relevant inputs for the Add Expense Feature : +![Sequence Diagram for Parser for Add Expense Feature](docs\diagram\sequenceDiagram_AddSavings.png) + +The following is a step-by-step explanation for the Parser for the Find Feature : +1. `BudgetBuddy` calls `Parser#parseCommand(input)` with `input` being the entire user input. +E.g `add savings c/Allowance a/20` +2. Within the `Parser`, it will have determined that the `input` is a Find Command from the `isAddSavingsCommand(input)` +function. +3. The `Parser` then self calls the method `handleAddExpenseCommand(input)` with the `input` still being the entire +user input. +4. Within `AddExpenseCommand(input)`, the first check would be the check for the existence of any combination of +`c/ , and a/`. If none of these combinations were found, it immediately returns `null`. +5. If the checks in `4.` is passed, two variables would be initialized. + + * | Variable Name | Variable Type | + |---------------|---------------| + | category | String | + | amount | String | +6. Depending on which parameters were present, the corresponding input would be extracted and placed into each variable +using the `Parser#extractDetailsForAdd(input, "parameter")` +7. Finally, `Parser#handleAddExpenseCommand()` intialises a `AddExpensesCommandCreator` which then returns `AddSavingCommand` to `Parser#parseCommand()`, which is then returned to `BudgetBuddy`. + ### Edit Expense Feature The Edit Expense feature allows users to edit their previously added expenses, specifically the `category`, `amount`, and `description`. This feature is managed by the `EditExpenseCommand` class, which is initialized by the @@ -745,108 +834,3 @@ type fast. It also provides the ability to deal with finances on a singular plat | v2.0 | user | view what expenses i have in each of my recurring expenses list | know what expenses i have put into each list | | v2.0 | user | remove a list from my recurring expenses list | remove underutilized lists or wrongly added lists | - -## Non-Functional Requirements - -{Give non-functional requirements} - -## Glossary - -* *glossary item* - Definition - -## Instructions for manual testing - -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} - -### Add Expense Feature - -#### Implementation - -The Add Expense Feature allows users to add expenses to different categories. `AddExpenseCommand` class enables this feature, -after initialized by the `Parser` class. Within the `AddExpense` object, the `Parser` would have initialized it with -4 variables, an `ExpenseList` object, along with a `category`, `amount` , `description`. -The relevance of these Class Attributes in `AddExpenseCommand` is as follows : - -| Class Attribute | Variable Type | Relevance | -|-----------------|---------------|---------------------------------------------------| -| expenses | ExpenseList | ExpenseList Object containing the list of expenses| -| category | String | The category that the `expense` belongs to | -| amount | String | The amount spent | -| description | String | The description of the expense | - - -Upon the call of the `execute()` method in `BudgetBuddy` using `command.execute()`, -the `AddExpenseCommand` Object utilizes the following method from the `ExpenseList` class to add it to the existing -list of `expenses` matching against the corresponding `category`. - -| Method | Return Type | Relevance | -|--------------|-------------|-------------------------------------------------| -| addExpense() | void | Add expense to the existing list of `expenses` | - -The following UML Sequence diagram shows how the Parser works to obtain the relevant inputs for the Add Expense Feature : -![Sequence Diagram for Parser for Add Expense Feature](docs\diagram\sequenceDiagram_AddExpense.png) - -The following is a step-by-step explanation for the Parser for the Find Feature : -1. `BudgetBuddy` calls `Parser#parseCommand(input)` with `input` being the entire user input. -E.g `add expense c/Transport a/20 d/EZ-Link Top Up` -2. Within the `Parser`, it will have determined that the `input` is a Find Command from the `isAddExpenseCommand(input)` -function. -3. The `Parser` then self calls the method `handleAddExpenseCommand(input)` with the `input` still being the entire -user input. -4. Within `AddExpenseCommand(input)`, the first check would be the check for the existence of any combination of -`c/ , a/ and d/`. If none of these combinations were found, it immediately returns `null`. -5. If the checks in `4.` is passed, Three variables would be initialized. - - * | Variable Name | Variable Type | - |---------------|---------------| - | category | String | - | amount | String | - | description | String | -6. Depending on which parameters were present, the corresponding input would be extracted and placed into each variable -using the `Parser#extractDetailsForAdd(input, "parameter")` -7. Finally, `Parser#handleAddExpenseCommand()` returns a `AddExpensesCommand` to `Parser#parseCommand()`, which is -then returned to `BudgetBuddy` - -### Add Savings Feature - -#### Implementation - -The Add Savings Feature allows users to add savings to different categories. `AddSavingCommandCreator` class intialises the `AddSavingCommand`, after initialised by the `Parser` class. Within the `AddSavings` object, the `Parser` would have initialized it with -4 variables, a `SavingList` object, along with a `category`, `amount`. -The relevance of these Class Attributes in `AddExpenseCommand` is as follows : - -| Class Attribute | Variable Type | Relevance | -|-----------------|---------------|---------------------------------------------------| -| savings | SavingList | SavingList Object containing the list of savings | -| category | String | The category that the `expense` belongs to | -| amount | String | The amount spent | - -Upon the call of the `execute()` method in `BudgetBuddy` using `command.execute()`, -the `AddSavingCommand` Object utilizes the following method from the `SavingList` class to add it to the existing -list of `savings` matching against the corresponding `category`. - -| Method | Return Type | Relevance | -|--------------|-------------|-------------------------------------------------| -| addSaving() | void | Add savings to the existing list of `savings` | - -The following UML Sequence diagram shows how the Parser works to obtain the relevant inputs for the Add Expense Feature : -![Sequence Diagram for Parser for Add Expense Feature](docs\diagram\sequenceDiagram_AddSavings.png) - -The following is a step-by-step explanation for the Parser for the Find Feature : -1. `BudgetBuddy` calls `Parser#parseCommand(input)` with `input` being the entire user input. -E.g `add savings c/Allowance a/20` -2. Within the `Parser`, it will have determined that the `input` is a Find Command from the `isAddSavingsCommand(input)` -function. -3. The `Parser` then self calls the method `handleAddExpenseCommand(input)` with the `input` still being the entire -user input. -4. Within `AddExpenseCommand(input)`, the first check would be the check for the existence of any combination of -`c/ , and a/`. If none of these combinations were found, it immediately returns `null`. -5. If the checks in `4.` is passed, two variables would be initialized. - - * | Variable Name | Variable Type | - |---------------|---------------| - | category | String | - | amount | String | -6. Depending on which parameters were present, the corresponding input would be extracted and placed into each variable -using the `Parser#extractDetailsForAdd(input, "parameter")` -7. Finally, `Parser#handleAddExpenseCommand()` intialises a `AddExpensesCommandCreator` which then returns `AddSavingCommand` to `Parser#parseCommand()`, which is then returned to `BudgetBuddy`. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index d92db6fae0..a158a6e7cc 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -58,31 +58,45 @@ Example of usage: `menu 1` : Displays commands related to feature associated to menu list item 1 ### Add Expense -Adds new expense +Records a new expense under a specific category with a detailed description. Format: `add expense c/CATEGORY a/AMOUNT d/DESCRIPTION` * Increments expense of the specified CATEGORY by AMOUNT given. -* The `CATEGORY` must be one of the following pre-defined categories: "Housing", - "Groceries", "Utility", "Transport", "Entertainment" or "Others". -* The `AMOUNT` must be a positive integer. -* The `DESCRIPTION` can be any string. +* The category under which the expense is to be recorded. It must match one of the + pre-defined categories exactly (not case-insensitive): + Housing + Groceries + Utility + Transport + Entertainment + Others +* The `AMOUNT` is the amount to add to the expense. It must be a positive number and can include + up to two decimal places. +* The `DESCRIPTION` is a brief description of the expense. Accepts any text string. Example of Usage: `add expense c/Entertainment a/167 d/Bruno Mars` - ### Add Savings -Increase savings by specified amount to the savings list +Adds a specified amount to the savings under a particular category. Format: `add savings c/CATEGORY a/AMOUNT` * Increments savings of the specified CATEGORY by AMOUNT given. -* The `CATEGORY` must be one of the following pre-defined categories: "Salary", - "Investments", "Gifts" or "Others". -* The `AMOUNT` must be a positive integer. -* The `DESCRIPTION` can be any string. +* The category for the savings increment. It must be one of the pre-defined + categories (not case-insensitive): + Salary + Investments + Gifts + Others +* The `AMOUNT` is the amount to add to the savings. It must be a positive number + and can include up to two decimal places. + +Example of Usage: + +`add savings c/Salary a/500.50` ### Add Split Expenses Add expenses that are meant for splitting among friends or colleague @@ -254,6 +268,9 @@ Format `settle i/Index` * The system will settle the splitted expense corresponding to `Index` * `Index` must be a positive integer +Example of usage: +`settle i/2`: Delete splitexpense of index 2 listed in splittedexpenses tracker + ### Finding expenses : `find expenses` Finds expenses based on their description or amount diff --git a/docs/diagrams/SequenceDiagram_addSavings.png b/docs/diagrams/SequenceDiagram_addSavings.png deleted file mode 100644 index 2e659fc41e..0000000000 Binary files a/docs/diagrams/SequenceDiagram_addSavings.png and /dev/null differ diff --git a/docs/diagrams/sequenceDiagram_AddExpense.jpg b/docs/diagrams/sequenceDiagram_AddExpense.jpg new file mode 100644 index 0000000000..c5fcb7ac8a Binary files /dev/null and b/docs/diagrams/sequenceDiagram_AddExpense.jpg differ diff --git a/docs/diagrams/sequenceDiagram_AddExpense.png b/docs/diagrams/sequenceDiagram_AddExpense.png deleted file mode 100644 index 0b345200a0..0000000000 Binary files a/docs/diagrams/sequenceDiagram_AddExpense.png and /dev/null differ diff --git a/docs/diagrams/sequenceDiagram_AddSaving.jpg b/docs/diagrams/sequenceDiagram_AddSaving.jpg new file mode 100644 index 0000000000..7e92ca27d0 Binary files /dev/null and b/docs/diagrams/sequenceDiagram_AddSaving.jpg differ diff --git a/docs/diagrams/sequenceDiagram_SplitExpense.jpg b/docs/diagrams/sequenceDiagram_SplitExpense.jpg new file mode 100644 index 0000000000..b371a2647b Binary files /dev/null and b/docs/diagrams/sequenceDiagram_SplitExpense.jpg differ diff --git a/docs/team/yyangdaa.md b/docs/team/yyangdaa.md index 6b8d068cca..6110db40f3 100644 --- a/docs/team/yyangdaa.md +++ b/docs/team/yyangdaa.md @@ -27,4 +27,21 @@ docs~functional-code~test-code~other) #### Enhancements to existing features: 1. Wrote JUnit tests for the ExpenseList, SavingsList, SplitExpenseList and Parser. +- Implemented Logging/Assertions for improved error handling. (Pull Requests : [#45](https://github.com/AY2324S2-CS2113-T12-3/tp/pull/45), +[#56](https://github.com/AY2324S2-CS2113-T12-3/tp/pull/56)). +#### Developer Guide +- Added implementation details of the `AddExpense`, `AddSaving` and `SplitExpense` feature. (Pull Requests : [#207](https://github.com/AY2324S2-CS2113-T12-3/tp/pull/207)). +- Added design details of the `AddExpense`, `AddSaving`, `SplitExpense`, `SplitExpenseList` and `SettleSplitExpenseList` classes. +(Pull Requests : [#207](https://github.com/AY2324S2-CS2113-T12-3/tp/pull/207)). +- Added user stories for my respective features. (Pull Requests : [#207](https://github.com/AY2324S2-CS2113-T12-3/tp/pull/207)). +- Added Sequence Diagrams for `AddExpense`, `AddSaving` and `SplitExpense` features. (Pull Requests : [#207](https://github.com/AY2324S2-CS2113-T12-3/tp/pull/207)). + +### Community + +#### Reported Bugs and Suggestions for Other Teams +- Provided 4 DG Peer Review Comments for another team. ([Team #1](https://github.com/nus-cs2113-AY2324S2/tp/pull/25)). +- Reported 5 Bugs for another team during PE-D. ([Team #1](https://github.com/nus-cs2113-AY2324S2/tp/pull/54)). + +### Tools +- Usage of Draw.io for my Sequence Diagrams. ([Draw.io](https://draw.io/)). \ No newline at end of file diff --git a/src/main/java/seedu/budgetbuddy/BudgetBuddy.java b/src/main/java/seedu/budgetbuddy/BudgetBuddy.java index 30ca65b9ae..223ed96cbf 100644 --- a/src/main/java/seedu/budgetbuddy/BudgetBuddy.java +++ b/src/main/java/seedu/budgetbuddy/BudgetBuddy.java @@ -21,6 +21,7 @@ public class BudgetBuddy { private Storage expensesStorage; private Storage savingsStorage; private Storage recurringExpensesStorage; + private Storage splitexpensesStorage; private Storage defaultCurrency; @@ -35,6 +36,7 @@ public BudgetBuddy() { expensesStorage = new Storage("./data/ExpenseFile.txt"); savingsStorage = new Storage("./data/SavingsFile.txt"); recurringExpensesStorage = new Storage("./data/RecurringExpensesFile.txt"); + splitexpensesStorage = new Storage("./data/SplitExpensesFile.txt"); defaultCurrency = new Storage("./data/DefaultCurrency.txt"); } @@ -53,6 +55,7 @@ public void handleCommands(String input) { expensesStorage.saveExpenses(expenses.getExpenses()); savingsStorage.saveSavings(savings.getSavings()); recurringExpensesStorage.saveRecurringExpenses(recurringExpenseLists); + splitexpensesStorage.saveSplitExpenses(splitexpenses.getSplitExpenses()); defaultCurrency.saveCurrency(); } catch (IOException e) { @@ -71,6 +74,7 @@ public void run() { defaultCurrency.loadCurrency(); this.expenses.getExpenses().addAll(expensesStorage.loadExpenses()); this.savings.getSavings().addAll(savingsStorage.loadSavings()); + this.splitexpenses.getSplitExpenses().addAll(splitexpensesStorage.loadSplitExpenses()); this.recurringExpenseLists = recurringExpensesStorage.loadRecurringExpensesList(); diff --git a/src/main/java/seedu/budgetbuddy/Parser.java b/src/main/java/seedu/budgetbuddy/Parser.java index f009f63c06..f3c70a2879 100644 --- a/src/main/java/seedu/budgetbuddy/Parser.java +++ b/src/main/java/seedu/budgetbuddy/Parser.java @@ -234,8 +234,8 @@ public Command parseCommand(ExpenseList expenses, SavingList savings, SplitExpen } if (isConvertCurrencyCommand(input.toLowerCase())) { - CommandCreator commandCreator = new ChangeCurrencyCommandCreator(input, savings, expenses, expensesList, - new CurrencyConverter()); + CommandCreator commandCreator = new ChangeCurrencyCommandCreator(input, savings, expenses, splitexpenses, + expensesList, new CurrencyConverter()); return commandCreator.createCommand(); } diff --git a/src/main/java/seedu/budgetbuddy/Storage.java b/src/main/java/seedu/budgetbuddy/Storage.java index 36b90fff73..5eab7421bd 100644 --- a/src/main/java/seedu/budgetbuddy/Storage.java +++ b/src/main/java/seedu/budgetbuddy/Storage.java @@ -46,25 +46,160 @@ private void ensureDirectoryExists() { } } - public List loadExpenses() throws FileNotFoundException { + /** + * Loads a list of expenses from a file. + * If an exception occurs during the loading process (e.g., if the file is corrupted), + * the expenses list file will be reset. + * + * @return A list of {@link Expense} objects loaded from the file. + * @throws IOException If an error occurs when accessing the file. + */ + public List loadExpenses() throws IOException { File file = new File(filePath); List expenses = new ArrayList<>(); Scanner scanner = new Scanner(file); - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - String[] parts = line.split("\\|"); - // Assuming the order is Date|Category|Amount|Description - LocalDate date = LocalDate.parse(parts[0].trim()); - String category = parts[1].trim(); - double amount = Double.parseDouble(parts[2].trim()); - String description = parts[3].trim(); - Expense expense = new Expense(date, category, amount, description); - expenses.add(expense); + try { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + String[] parts = line.split("\\|"); + LocalDate date = LocalDate.parse(parts[0].trim()); + String category = parts[1].trim(); + double amount = Double.parseDouble(parts[2].trim()); + String description = parts[3].trim(); + Expense expense = new Expense(date, category, amount, description); + expenses.add(expense); + } + } catch (Exception e) { + LOGGER.log(Level.INFO, "Exception successfully caught. Error has been handled"); + System.out.println(e.getMessage()); + System.out.println("Your Expenses File is corrupted, resetting the file...."); + resetExpenseListFile(); + return expenses; + } finally { + scanner.close(); } - scanner.close(); return expenses; } + /** + * Saves a list of expenses to a file. + * If an IOException occurs, the expenses list file will be reset. + * + * @param expenses A list of {@link Expense} objects to save to the file. + * @throws IOException If an error occurs during writing to the file. + */ + public void saveExpenses(List expenses) throws IOException { + ensureDirectoryExists(); // Ensure directory and file exist before writing + FileWriter writer = null; + try { + writer = new FileWriter(filePath, false); + for (Expense expense : expenses) { + writer.write(String.format("%s | %s | %.2f | %s\n", + expense.getDateAdded(), expense.getCategory(), expense.getAmount(), expense.getDescription())); + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "IOException occurred while saving expenses. " + + "Resetting expense list file.", e); + resetExpenseListFile(); // Reset the expense list file if an exception occurs + throw e; // Re-throw the exception to indicate that saving was not successful + } finally { + if (writer != null) { + writer.close(); + } + } + } + + /** + * Resets the expense list file. If the file exists, it is deleted and a new empty file is created. + * + * @throws IOException If deleting the existing file or creating a new file fails. + */ + public void resetExpenseListFile() throws IOException { + File file = new File(filePath); + file.delete(); + file.createNewFile(); + FileWriter writer = new FileWriter(filePath, false); + writer.write(""); + writer.close(); + } + + /** + * Loads a list of savings from the specified file. + * If an exception occurs during the loading process (e.g., if the file is corrupted), + * the savings list file will be reset. + * + * @return A list of {@link Saving} objects representing the savings loaded from the file. + * @throws IOException If there is an issue with file access that prevents the method from reading the savings. + */ + public List loadSavings() throws IOException { + List savings = new ArrayList<>(); + File file = new File(filePath); + Scanner scanner = new Scanner(file); + try { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + String[] parts = line.split("\\|"); + String category = parts[0].trim(); + double amount = Double.parseDouble(parts[1].trim()); + Saving saving = new Saving(category, amount); + savings.add(saving); + } + } catch (Exception e) { + LOGGER.log(Level.INFO, "Exception caught while loading savings. Resetting savings list file.", e); + System.out.println(e.getMessage()); + System.out.println("Your Savings File is corrupted, resetting the file...."); + resetSavingsListFile(); + return savings; + } finally { + scanner.close(); + } + return savings; + } + + /** + * Saves the list of savings to the specified file. + * If an IOException occurs, the savings list file will be reset. + * + * @param savings A list of {@link Saving} objects that represent the savings to save to the file. + * @throws IOException If an IOException occurs during file writing, indicating the savings could not be saved. + */ + public void saveSavings(List savings) throws IOException { + ensureDirectoryExists(); // Ensure directory and file exist before writing + FileWriter writer = null; + try { + writer = new FileWriter(filePath, false); + for (Saving saving : savings) { + writer.write(String.format("%s | %.2f\n", + saving.getCategory(), saving.getAmount())); + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "IOException occurred while saving savings. Resetting savings list file.", e); + resetSavingsListFile(); + throw e; + } finally { + if (writer != null) { + writer.close(); + } + } + } + + /** + * Resets the savings list file. If the file exists, it is deleted, and a new empty file is created. + * This method is typically called when the file is found to be corrupted or + * when an issue arises during file operations. + * + * @throws IOException If there is an issue with file access that prevents the method + * from deleting or creating the file. + */ + public void resetSavingsListFile() throws IOException { + File file = new File(filePath); + file.delete(); + file.createNewFile(); + FileWriter writer = new FileWriter(filePath, false); + writer.write(""); + writer.close(); + } + public void resetRecurringExpensesListFile() throws IOException { File file = new File(filePath); file.delete(); @@ -171,47 +306,7 @@ public void saveRecurringExpenses(RecurringExpenseLists recurringExpenseLists) ", file has been reinitialized. Run a command to save your recurringexpenses"); } - } - - public void saveExpenses(List expenses) throws IOException { - ensureDirectoryExists(); // Ensure directory and file exist before writing - FileWriter writer = new FileWriter(filePath, false); // Overwrite the file - for (Expense expense : expenses) { - writer.write(String.format("%s | %s | %.2f | %s\n", - expense.getDateAdded(), expense.getCategory(), expense.getAmount(), expense.getDescription())); - } - writer.close(); - } - - - - // Inside Storage.java - public List loadSavings() throws FileNotFoundException { - File file = new File(filePath); - List savings = new ArrayList<>(); - Scanner scanner = new Scanner(file); - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - String[] parts = line.split("\\|"); - // Assuming the order is Category|Amount - String category = parts[0].trim(); - double amount = Double.parseDouble(parts[1].trim()); - Saving saving = new Saving(category, amount); - savings.add(saving); - } - scanner.close(); - return savings; - } - - public void saveSavings(List savings) throws IOException { - ensureDirectoryExists(); // Ensure directory and file exist before writing - FileWriter writer = new FileWriter(filePath, false); // Overwrite the file - for (Saving saving : savings) { - writer.write(String.format("%s | %.2f\n", - saving.getCategory(), saving.getAmount())); - } - writer.close(); - } + } /** * Saves the default currency to the specified file path. @@ -235,7 +330,6 @@ public void saveCurrency() throws IOException { } } - public List loadSplitExpenses() throws FileNotFoundException { File file = new File(filePath); List splitExpenses = new ArrayList<>(); @@ -243,29 +337,31 @@ public List loadSplitExpenses() throws FileNotFoundException { while (scanner.hasNextLine()) { String line = scanner.nextLine(); String[] parts = line.split("\\|"); - // Assuming the order is Date|Amount|Number of People|Description - String amount = parts[1].trim(); - String numberOfPeople = parts[2].trim(); + LocalDate date = LocalDate.parse(parts[0].trim()); + double amount = Double.parseDouble(parts[1].trim()); + int numberOfPeople = Integer.parseInt(parts[2].trim()); String description = parts[3].trim(); - SplitExpense splitExpense = new SplitExpense(amount, numberOfPeople, description); + SplitExpense splitExpense = new SplitExpense(date, amount, numberOfPeople, description); splitExpenses.add(splitExpense); } scanner.close(); return splitExpenses; } + public void saveSplitExpenses(List splitExpenses) throws IOException { - - ensureDirectoryExists(); - - FileWriter writer = new FileWriter(filePath, false); + ensureDirectoryExists(); + + FileWriter writer = new FileWriter(filePath, false); // Overwrite the file for (SplitExpense splitExpense : splitExpenses) { - writer.write(String.format("%s | %s | %s\n", - splitExpense.getAmount(), splitExpense.getNumberOfPeople(), splitExpense.getDescription())); + writer.write(String.format("%s | %.2f | %d | %s\n", + splitExpense.getDateAdded().toString(), + splitExpense.getAmount(), + splitExpense.getNumberOfPeople(), + splitExpense.getDescription())); } writer.close(); - } - + } /** * Loads currency data from the specified file path and sets the default currency accordingly. diff --git a/src/main/java/seedu/budgetbuddy/command/AddExpenseCommand.java b/src/main/java/seedu/budgetbuddy/command/AddExpenseCommand.java index b8a19a7954..3b2c0e1b3f 100644 --- a/src/main/java/seedu/budgetbuddy/command/AddExpenseCommand.java +++ b/src/main/java/seedu/budgetbuddy/command/AddExpenseCommand.java @@ -22,7 +22,7 @@ public AddExpenseCommand (ExpenseList expenses,String category, String amount, S public void execute() { try { expenses.addExpense(this.category,this.amount,this.description); - System.out.println("Expense Added :" + category + " of $" + amount + " description : " + description); + System.out.println("Expense Added: " + category + " of $" + amount + " description: " + description); } catch (BudgetBuddyException e) { System.out.println(e.getMessage()); } diff --git a/src/main/java/seedu/budgetbuddy/command/AddSavingCommand.java b/src/main/java/seedu/budgetbuddy/command/AddSavingCommand.java index 279a7ab000..7fe456f317 100644 --- a/src/main/java/seedu/budgetbuddy/command/AddSavingCommand.java +++ b/src/main/java/seedu/budgetbuddy/command/AddSavingCommand.java @@ -3,11 +3,7 @@ import seedu.budgetbuddy.commons.SavingList; import seedu.budgetbuddy.exception.BudgetBuddyException; -import java.util.logging.Logger; -import java.util.logging.Level; - public class AddSavingCommand extends Command { - private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); private SavingList savings; private final String category; @@ -22,15 +18,11 @@ public AddSavingCommand(SavingList savings, String category, String amount) { @Override public void execute(){ try { - LOGGER.log(Level.INFO, "Adding savings to category: {0} with amount: ${1}", new Object[]{category, amount}); savings.addSaving(this.category, this.amount); - - LOGGER.log(Level.INFO, "Savings added to: {0} of ${1}", new Object[]{category, amount}); - System.out.println("Savings Added to:" + category + " of $" + amount); + System.out.println("Savings Added to: " + category + " of $" + amount); } catch (BudgetBuddyException e) { System.out.println(e.getMessage()); - LOGGER.log(Level.SEVERE, "Exception while adding savings", e); } } } diff --git a/src/main/java/seedu/budgetbuddy/command/ChangeCurrencyCommand.java b/src/main/java/seedu/budgetbuddy/command/ChangeCurrencyCommand.java index d422447360..3bbc112724 100644 --- a/src/main/java/seedu/budgetbuddy/command/ChangeCurrencyCommand.java +++ b/src/main/java/seedu/budgetbuddy/command/ChangeCurrencyCommand.java @@ -4,6 +4,7 @@ import seedu.budgetbuddy.commons.DefaultCurrency; import seedu.budgetbuddy.commons.ExpenseList; import seedu.budgetbuddy.commons.SavingList; +import seedu.budgetbuddy.commons.SplitExpenseList; import seedu.budgetbuddy.commons.RecurringExpenseLists; import java.util.Currency; @@ -13,14 +14,17 @@ public class ChangeCurrencyCommand extends Command { private Currency newCurrency; private SavingList savings; private ExpenseList expenses; + private SplitExpenseList splitExpenses; private RecurringExpenseLists recurringExpenseLists; private CurrencyConverter currencyConverter; - public ChangeCurrencyCommand(Currency newCurrency, SavingList savings, ExpenseList expenses, - RecurringExpenseLists recurringExpenseLists, CurrencyConverter currencyConverter) { + public ChangeCurrencyCommand(Currency newCurrency, SavingList savings, ExpenseList expenses, SplitExpenseList + splitExpenses, RecurringExpenseLists recurringExpenseLists, + CurrencyConverter currencyConverter) { this.newCurrency = newCurrency; this.savings = savings; this.expenses = expenses; + this.splitExpenses = splitExpenses; this.recurringExpenseLists = recurringExpenseLists; this.currencyConverter = currencyConverter; } @@ -29,6 +33,7 @@ public ChangeCurrencyCommand(Currency newCurrency, SavingList savings, ExpenseLi public void execute() { currencyConverter.convertSavingCurrency(newCurrency, savings); currencyConverter.convertExpenseCurrency(newCurrency, expenses); + currencyConverter.convertSplitExpenseCurrency(newCurrency, splitExpenses); currencyConverter.convertRecurringExpensesCurrency(newCurrency, recurringExpenseLists); currencyConverter.convertBudgetCurrency(newCurrency, expenses); DefaultCurrency.setDefaultCurrency(newCurrency); diff --git a/src/main/java/seedu/budgetbuddy/commandcreator/AddExpenseCommandCreator.java b/src/main/java/seedu/budgetbuddy/commandcreator/AddExpenseCommandCreator.java index eeb07f6bc1..ed4a05ed4e 100644 --- a/src/main/java/seedu/budgetbuddy/commandcreator/AddExpenseCommandCreator.java +++ b/src/main/java/seedu/budgetbuddy/commandcreator/AddExpenseCommandCreator.java @@ -46,6 +46,10 @@ public Command handleAddExpenseCommand(ExpenseList expenses, String input) { System.out.println("amount is missing."); return null; } + if (input.contains("!") || input.contains("|")) { + System.out.println("Please do not include a ! or | in your input"); + return null; + } try { double amountValue = Double.parseDouble(amount); diff --git a/src/main/java/seedu/budgetbuddy/commandcreator/AddSavingCommandCreator.java b/src/main/java/seedu/budgetbuddy/commandcreator/AddSavingCommandCreator.java index f3a072ac58..ed65fb17c2 100644 --- a/src/main/java/seedu/budgetbuddy/commandcreator/AddSavingCommandCreator.java +++ b/src/main/java/seedu/budgetbuddy/commandcreator/AddSavingCommandCreator.java @@ -56,7 +56,7 @@ public Command handleAddSavingCommand(SavingList savings, String input) { try { double amountValue = Double.parseDouble(amount); if (amountValue <= 0) { - throw new BudgetBuddyException(amount + " is not a valid amount."); + throw new BudgetBuddyException(amount + " is negative. Please enter a positive amount."); } } catch (NumberFormatException e) { diff --git a/src/main/java/seedu/budgetbuddy/commandcreator/ChangeCurrencyCommandCreator.java b/src/main/java/seedu/budgetbuddy/commandcreator/ChangeCurrencyCommandCreator.java index 76d8ce7c5f..dcae145f94 100644 --- a/src/main/java/seedu/budgetbuddy/commandcreator/ChangeCurrencyCommandCreator.java +++ b/src/main/java/seedu/budgetbuddy/commandcreator/ChangeCurrencyCommandCreator.java @@ -4,6 +4,7 @@ import seedu.budgetbuddy.commons.ExpenseList; import seedu.budgetbuddy.commons.RecurringExpenseLists; import seedu.budgetbuddy.commons.SavingList; +import seedu.budgetbuddy.commons.SplitExpenseList; import seedu.budgetbuddy.command.ChangeCurrencyCommand; import seedu.budgetbuddy.command.Command; @@ -16,16 +17,19 @@ public class ChangeCurrencyCommandCreator extends CommandCreator { private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); private ExpenseList expenses; private SavingList savings; + private SplitExpenseList splitExpenses; private RecurringExpenseLists recurringExpenseLists; private String input; private CurrencyConverter newCurrency; - public ChangeCurrencyCommandCreator(String input, SavingList savings, ExpenseList expenses - , RecurringExpenseLists recurringExpenseLists, CurrencyConverter newCurrency) { + public ChangeCurrencyCommandCreator(String input, SavingList savings, ExpenseList expenses, + SplitExpenseList splitExpenses, RecurringExpenseLists recurringExpenseLists, + CurrencyConverter newCurrency) { this.input = input; this.savings = savings; this.expenses = expenses; + this.splitExpenses = splitExpenses; this.recurringExpenseLists = recurringExpenseLists; this.newCurrency = newCurrency; @@ -42,6 +46,7 @@ public ChangeCurrencyCommandCreator(String input, SavingList savings, ExpenseLis * @return A ChangeCurrencyCommand if the input is valid; otherwise, null. */ public Command handleChangeCurrencyCommand(String input, SavingList savingList, ExpenseList expenseList, + SplitExpenseList splitExpenses, RecurringExpenseLists recurringExpenseLists, CurrencyConverter currencyConverter) { if (input.toLowerCase().startsWith("change currency")) { @@ -56,8 +61,8 @@ public Command handleChangeCurrencyCommand(String input, SavingList savingList, Currency newCurrency = Currency.getInstance(currencyCode.toUpperCase()); assert newCurrency != null : "Currency code should be valid"; LOGGER.log(Level.INFO, "Default currency changed to " + newCurrency); - return new ChangeCurrencyCommand(newCurrency, savingList, expenseList, recurringExpenseLists, - currencyConverter); + return new ChangeCurrencyCommand(newCurrency, savingList, expenseList, splitExpenses, + recurringExpenseLists, currencyConverter); } catch (IllegalArgumentException e) { LOGGER.log(Level.WARNING, "Invalid currency code: " + currencyCode); System.out.println("Invalid currency code."); @@ -73,6 +78,6 @@ public Command handleChangeCurrencyCommand(String input, SavingList savingList, } @Override public Command createCommand() { - return handleChangeCurrencyCommand(input, savings, expenses, recurringExpenseLists, newCurrency); + return handleChangeCurrencyCommand(input, savings, expenses, splitExpenses, recurringExpenseLists, newCurrency); } } diff --git a/src/main/java/seedu/budgetbuddy/commandcreator/EditExpenseCommandCreator.java b/src/main/java/seedu/budgetbuddy/commandcreator/EditExpenseCommandCreator.java index aa47ef43cf..fab81c6da4 100644 --- a/src/main/java/seedu/budgetbuddy/commandcreator/EditExpenseCommandCreator.java +++ b/src/main/java/seedu/budgetbuddy/commandcreator/EditExpenseCommandCreator.java @@ -3,6 +3,7 @@ import seedu.budgetbuddy.commons.ExpenseList; import seedu.budgetbuddy.command.Command; import seedu.budgetbuddy.command.EditExpenseCommand; +import seedu.budgetbuddy.exception.BudgetBuddyException; public class EditExpenseCommandCreator extends CommandCreator { private ExpenseList expenses; @@ -23,6 +24,15 @@ public EditExpenseCommandCreator(String input, ExpenseList expenses) { * is invalid or incomplete. */ public Command handleEditExpenseCommand(ExpenseList expenses, String input) { + try { + checkForInvalidInputs(input); + checkForValidCategory(input); + checkForInvalidAmount(input); + } catch (IllegalArgumentException | BudgetBuddyException e) { + System.out.println(e.getMessage()); + System.out.println("Command Format : edit expense c/CATEGORY i/INDEX a/AMOUNT d/DESCRIPTION"); + return null; + } String[] parts = input.split(" "); String category = null; int index = -1; @@ -33,34 +43,90 @@ public Command handleEditExpenseCommand(ExpenseList expenses, String input) { if (part.startsWith("c/")) { category = part.substring(2); } else if (part.startsWith("i/")) { - try { - index = Integer.parseInt(part.substring(2)); - } catch (NumberFormatException e) { - // Handle invalid index format - return null; - } + index = Integer.parseInt(part.substring(2)); // Removed the redundant try-catch block } else if (part.startsWith("a/")) { - try { - amount = Double.parseDouble(part.substring(2)); - } catch (NumberFormatException e) { - // Handle invalid amount format - System.out.println("Invalid Amount. Amount should be a numerical value."); - return null; - } + amount = Double.parseDouble(part.substring(2)); } else if (part.startsWith("d/")) { description = part.substring(2); } } - // Validate required fields if (category != null && index != -1 && amount != -1 && description != null) { return new EditExpenseCommand(expenses, category, index, amount, description); } else { // Handle incomplete command + System.out.println("Incomplete command. Please ensure all parameters are included."); return null; } } + + public static void checkForInvalidInputs (String input) throws BudgetBuddyException { + final String categoryPrefix = "c/"; + final String indexPrefix = "i/"; + final String amountPrefix = "a/"; + final String descriptionPrefix = "d/"; + + if (input.contains("!") || input.contains("|")) { + throw new BudgetBuddyException("Please do not include a ! or | in your input"); + } + if (!input.contains("c/") || !input.contains("i/") || !input.contains("a/") || !input.contains("d/")) { + throw new IllegalArgumentException("Please Ensure that you include c/, i/, a/ and d/"); + } + + String [] parameters = {categoryPrefix, indexPrefix, amountPrefix, descriptionPrefix}; + + for (String parameter : parameters) { + if (input.indexOf(parameter) != input.lastIndexOf(parameter)) { + throw new BudgetBuddyException("Please ensure that you do not have duplicate parameters."); + } + } + } + + public static void checkForValidCategory (String input) throws BudgetBuddyException { + String[] parts = input.split(" "); + String category = null; + for (String part : parts) { + if (part.startsWith("c/")) { + category = part.substring(2); + break; + } + } + + if (category == null || !(category.equals("Transport") || category.equals("Housing") || + category.equals("Groceries") || category.equals("Utility") || + category.equals("Entertainment") || category.equals("Others"))) { + throw new BudgetBuddyException("Please enter a valid category: Housing, Groceries, Utility, Transport," + + "Entertainment or Others "); + } + + } + + public static void checkForInvalidAmount(String input) throws BudgetBuddyException { + String[] parts = input.split(" "); + double amount = -1; + + for (String part : parts) { + if (part.startsWith("a/")) { + try { + amount = Double.parseDouble(part.substring(2)); + if (amount <= 0) { // Amount must be greater than 0 + throw new BudgetBuddyException("Invalid Amount. Amount must be greater than 0."); + } + break; // Break after finding the amount to stop checking other parts + } catch (NumberFormatException e) { + throw new BudgetBuddyException("Invalid Amount. Amount should be a numerical value."); + } + } + } + + if (amount == -1) { + // If amount is still -1, it means no amount was entered + throw new BudgetBuddyException("No amount specified. Please enter an amount using a/ prefix."); + } + } + + @Override public Command createCommand() { return handleEditExpenseCommand(expenses, input); diff --git a/src/main/java/seedu/budgetbuddy/commandcreator/SplitExpenseCommandCreator.java b/src/main/java/seedu/budgetbuddy/commandcreator/SplitExpenseCommandCreator.java index 53c40e8ec3..18ae290a43 100644 --- a/src/main/java/seedu/budgetbuddy/commandcreator/SplitExpenseCommandCreator.java +++ b/src/main/java/seedu/budgetbuddy/commandcreator/SplitExpenseCommandCreator.java @@ -5,84 +5,72 @@ import seedu.budgetbuddy.command.SplitExpenseCommand; import seedu.budgetbuddy.exception.BudgetBuddyException; -public class SplitExpenseCommandCreator extends CommandCreator{ +public class SplitExpenseCommandCreator extends CommandCreator { private SplitExpenseList splitexpenses; private String input; - /** - * Creates a SplitExpenseCommandCreator object. - * - * @param splitexpenses The list of split expenses. - * @param input The input string. - */ public SplitExpenseCommandCreator(SplitExpenseList splitexpenses, String input) { this.splitexpenses = splitexpenses; this.input = input; } - /** - * Parses the input and creates a new SplitExpenseCommand object. - * - * @param splitexpenses The list of split expenses. - * @param input The input string. - * @return The SplitExpenseCommand object. - */ public Command handleSplitExpenseCommand(SplitExpenseList splitexpenses, String input) { if (input == null || !input.contains("a/") || !input.contains("n/") || !input.contains("d/")) { System.out.println("Invalid command format."); return null; } - // Extract details directly using the prefixes - String amount = extractDetail(input, "a/"); - String numberOfPeople = extractDetail(input, "n/"); - String description = extractDetail(input, "d/"); + String amount = extractDetail(input, "a/", "n/"); + String numberOfPeople = extractDetail(input, "n/", "d/"); + String description = extractDetail(input, "d/", null); // Description is last, so no nextPrefix - // Validation for each part - if (amount.isEmpty() || numberOfPeople.isEmpty() || description.isEmpty()) { + if (amount.isEmpty()|| numberOfPeople.isEmpty() || description.isEmpty()) { System.out.println("Missing details."); return null; } + double amountValue; try { - double amountValue = Double.parseDouble(amount); - if (amountValue <= 0) { - throw new BudgetBuddyException(amount + " is not a valid amount."); + amountValue = Double.parseDouble(amount); + if (amountValue <= 0 || amountValue > 1_000_000_000_000D) { + throw new BudgetBuddyException(amount + " is not a valid amount. Amount must be positive and" + + " less than or equal" + + " to 1,000,000,000,000."); + } + if (!amount.matches("^\\d+(\\.\\d{1,2})?$")) { + throw new BudgetBuddyException("Amount must be a number with up to 2 decimal places."); } } catch (NumberFormatException | BudgetBuddyException e) { - System.out.println("Invalid amount format."); + System.out.println(e.getMessage()); return null; } + int numberValue; try { - int numberValue = Integer.parseInt(numberOfPeople); + numberValue = Integer.parseInt(numberOfPeople); if (numberValue <= 0) { throw new BudgetBuddyException(numberOfPeople + " is not a valid number."); } } catch (NumberFormatException | BudgetBuddyException e) { - System.out.println("Invalid number format."); + System.out.println(e.getMessage()); return null; } return new SplitExpenseCommand(splitexpenses, amount, numberOfPeople, description); } - /** - * Extracts the detail from the input string using the prefix. - * - * @param input The input string. - * @param prefix The prefix to search for. - * @return The detail extracted from the input string. - */ - private String extractDetail(String input, String prefix) { + private String extractDetail(String input, String prefix, String nextPrefix) { try { int startIndex = input.indexOf(prefix) + prefix.length(); - int endIndex = input.indexOf(" ", startIndex); - endIndex = endIndex == -1 ? input.length() : endIndex; // Handle last detail case - return input.substring(startIndex, endIndex); + int endIndex = nextPrefix != null ? input.indexOf(nextPrefix, startIndex) : input.length(); + if (endIndex == -1) { + endIndex = input.length(); + } + String detail = input.substring(startIndex, endIndex).trim(); + return detail.isEmpty() ? "" : detail; } catch (Exception e) { - return ""; // Return empty string if any error occurs + return ""; } } diff --git a/src/main/java/seedu/budgetbuddy/commons/CurrencyConverter.java b/src/main/java/seedu/budgetbuddy/commons/CurrencyConverter.java index 11f2f7c24d..a955b78313 100644 --- a/src/main/java/seedu/budgetbuddy/commons/CurrencyConverter.java +++ b/src/main/java/seedu/budgetbuddy/commons/CurrencyConverter.java @@ -102,6 +102,38 @@ public void convertExpenseCurrency(Currency newCurrency, ExpenseList expenses) { } } + public void convertSplitExpenseCurrency(Currency newCurrency, SplitExpenseList splitExpenses) { + if (splitExpenses == null) { + throw new IllegalArgumentException("SplitExpenseList cannot be null"); + } + + assert splitExpenses != null : "SplitExpenseList cannot be null"; + + if (DefaultCurrency.getDefaultCurrency() == newCurrency) { + System.out.println("Same currency for Split Expenses. No Conversion needed"); + return; + } else { // Convert the currency of each split expense in the SplitExpenseList + for (SplitExpense splitExpense : splitExpenses.getSplitExpenses()) { + if (splitExpense == null) { + LOGGER.warning("Skipping null split expense"); + System.out.println("Skipping null split expense"); + continue; + } + + try { + double convertedAmount = convertAmount(splitExpense.getAmount(), splitExpense.getCurrency(), + newCurrency); + splitExpense.setAmount(convertedAmount); + splitExpense.setCurrency(newCurrency); + } catch (IllegalArgumentException e) { + LOGGER.severe("Error converting amount for split expense: " + e.getMessage()); + System.out.println("Error converting amount for split expense: " + e.getMessage()); + } + } + System.out.println("Default currency for Split Expenses changed to " + newCurrency); + } + } + /** * Converts the currency of savings in the given SavingList to the specified new currency. * No conversion necessary if trying to convert to the same currency. @@ -139,38 +171,6 @@ public void convertSavingCurrency(Currency newCurrency, SavingList savings) { } } - public void convertSplitExpenseCurrency(Currency newCurrency, SplitExpenseList splitExpenses) { - if (splitExpenses == null) { - throw new IllegalArgumentException("SplitExpenseList cannot be null"); - } - - if (DefaultCurrency.getDefaultCurrency() == newCurrency) { - System.out.println("Same currency for Split Expenses. No Conversion needed"); - return; - } - - for (SplitExpense splitExpense : splitExpenses.getSplitExpenses()) { - if (splitExpense == null) { - LOGGER.warning("Skipping null split expense"); - System.out.println("Skipping null split expense"); - continue; - } - - try { - double convertedAmount = convertAmount(splitExpense.getAmount(), splitExpense.getCurrency(), - newCurrency); - splitExpense.setAmount(convertedAmount); - splitExpense.setCurrency(newCurrency); - } catch (IllegalArgumentException e) { - // Handle any IllegalArgumentException thrown during conversion - LOGGER.severe("Error converting amount for split expense: " + e.getMessage()); - System.out.println("Error converting amount for split expense: " + e.getMessage()); - } - } - - System.out.println("Default currency for Split Expenses changed to " + newCurrency); - } - public void convertRecurringExpensesCurrency(Currency newCurrency, RecurringExpenseLists recurringExpenseLists) { if (recurringExpenseLists == null) { @@ -192,7 +192,7 @@ public void convertRecurringExpensesCurrency(Currency newCurrency, RecurringExpe System.out.println("Default currency for Recurring Expenses changed to " + newCurrency); } - + public void convertBudgetCurrency(Currency newCurrency, ExpenseList expenseList) { if (expenseList == null) { throw new IllegalArgumentException("ExpenseList cannot be null"); diff --git a/src/main/java/seedu/budgetbuddy/commons/ExpenseList.java b/src/main/java/seedu/budgetbuddy/commons/ExpenseList.java index dedca99ea6..81f6c30be9 100644 --- a/src/main/java/seedu/budgetbuddy/commons/ExpenseList.java +++ b/src/main/java/seedu/budgetbuddy/commons/ExpenseList.java @@ -18,18 +18,19 @@ public class ExpenseList { private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); - + private static final double MAX_AMOUNT = 1_000_000_000_000.00; protected ArrayList expenses; protected ArrayList categories = new ArrayList<>(Arrays.asList("Housing", "Groceries", "Utility", "Transport", "Entertainment", "Others")); protected List budgets; + Ui ui = new Ui(); public ExpenseList(ArrayList expenses) { this.expenses = expenses; this.budgets = new ArrayList<>(); - + } public ExpenseList() { @@ -76,7 +77,7 @@ public ArrayList filterExpenses(String description, Double minAmount, D String descriptionInLowerCase = description.toLowerCase(); ArrayList filteredExpenses = new ArrayList<>(this.expenses.stream() .filter(expense -> (expense.getDescription() - .toLowerCase().contains(descriptionInLowerCase))) + .toLowerCase().contains(descriptionInLowerCase))) .filter(expense -> (minAmount == null || expense.getAmount() >= minAmount)) .filter(expense -> (maxAmount == null || expense.getAmount() <= maxAmount)) .collect(Collectors.toList())); @@ -102,6 +103,7 @@ public void listExpenses(String filterCategory) { for (int i = 0; i < expenses.size(); i++) { Expense expense = expenses.get(i); + // Checks for null expenses if (expense == null) { LOGGER.warning("Expense object at index " + i + " is null"); continue; @@ -118,6 +120,7 @@ public void listExpenses(String filterCategory) { ui.printDivider(); System.out.println("Overall Total Expenses: $" + String.format("%.2f", calculateTotalExpenses())); + // Assertion: Check if total expenses calculation is correct double totalExpenses = calculateTotalExpenses(); assert totalExpenses >= 0 : "Total expenses should be non-negative"; } catch (Exception e) { @@ -145,6 +148,7 @@ public double calculateTotalExpenses() { LOGGER.log(Level.WARNING, "Negative expense amount detected", e); } + // Assertion: Check if total expenses is non-negative assert totalExpenses >= 0 : "Total expenses should be non-negative"; return totalExpenses; @@ -155,51 +159,36 @@ public void addExpense(String category, String amount, String description) throw assert category != null : "Category should not be null"; assert amount != null : "Amount should not be null"; assert description != null : "Description should not be null"; - - if (!categories.contains(category)) { - throw new BudgetBuddyException("The category '" + category + "' is not listed."); + + String matchedCategory = categories.stream() + .filter(existingCategory -> existingCategory.equalsIgnoreCase(category)) + .findFirst() + .orElseThrow(() -> new BudgetBuddyException("The category '" + category + "' is not listed.")); + + if (!amount.matches("^\\d+(\\.\\d{1,2})?$")) { + throw new BudgetBuddyException("Invalid amount format. Amount should be a positive number with up" + + " to maximum two decimal places."); } + double amountAsDouble; try { amountAsDouble = Double.parseDouble(amount); } catch (NumberFormatException e) { throw new BudgetBuddyException("Invalid amount format. Amount should be a number."); } - + if (amountAsDouble < 0) { throw new BudgetBuddyException("Expenses should not be negative."); } - - // Check against the budget before adding the expense - Budget budgetForCategory = budgets.stream() - .filter(budget -> budget.getCategory().equalsIgnoreCase(category)) - .findFirst() - .orElse(null); - - if (budgetForCategory != null) { - double totalSpent = expenses.stream() - .filter(expense -> expense.getCategory().equalsIgnoreCase(category)) - .mapToDouble(Expense::getAmount) - .sum(); - double projectedTotal = totalSpent + amountAsDouble; - - if (projectedTotal > budgetForCategory.getBudget()) { - ui.printDivider(); - System.out.println("Warning: Adding this expense will exceed your budget for " + category); - ui.printDivider(); - - // Replace with actual user confirmation in your application context - if (!ui.getUserConfirmation()) { - System.out.println("Expense not added due to budget constraints."); - return; // Exit without adding the expense - } - } + + if (amountAsDouble > MAX_AMOUNT) { + throw new BudgetBuddyException("Amount exceeds the maximum allowed limit of " + MAX_AMOUNT); } - - Expense expense = new Expense(category, amountAsDouble, description); + + Expense expense = new Expense(matchedCategory, amountAsDouble, description); expenses.add(expense); - } + /** * Edits an expense entry in the expenses list at the specified index. Updates the category, @@ -234,7 +223,7 @@ public void editExpense(String category, int index, double amount, String descri // Check if the index is within valid bounds if (index <= 0 || index > expenses.size()) { LOGGER.warning("Invalid index: " + index); - System.out.println("Invalid index."); + System.out.println("Invalid index. Enter \"List Expenses\" to view the index."); return; } @@ -268,21 +257,18 @@ public String getName() { return "placeholder"; } - public void setBudget(String category, double budget) { + public void setBudget(String category, double budget){ LOGGER.info("Setting budget - Category: " + category + ", Budget: $" + budget); - - for (Budget b : budgets) { - if (b.getCategory().equalsIgnoreCase(category)) { + for (Budget b : budgets){ + if (b.getCategory().equalsIgnoreCase(category)){ LOGGER.info("Updating budget for category: " + category); b.setBudget(budget); System.out.println("Updated budget for " + category + " to $" + budget); return; } } - LOGGER.info("Creating new budget for category: " + category); budgets.add(new Budget(category, budget)); - System.out.println("Budget Added: " + category + " of $" + budget); } /** @@ -290,32 +276,29 @@ public void setBudget(String category, double budget) { * The expenses are sorted from the highest to the lowest amount, displaying the amount and what percentage * of the total budget each expense constitutes. * - * @param inputCategory The category for which to retrieve and print the budget and expenses. + * @param category The category for which to retrieve and print the budget and expenses. */ - public void getBudgetAndListExpensesForCategory(String inputCategory) { - // Trim the input and replace multiple internal spaces with a single space - String normalizedCategory = inputCategory.trim().replaceAll("\\s+", " "); - + public void getBudgetAndListExpensesForCategory(String category) { Budget budgetForCategory = budgets.stream() - .filter(budget -> budget.getCategory().equalsIgnoreCase(normalizedCategory)) + .filter(budget -> budget.getCategory().equalsIgnoreCase(category)) .findFirst() .orElse(null); if (budgetForCategory == null) { - System.out.println("No budget set for " + normalizedCategory); + System.out.println("No budget set for " + category); return; } double budgetAmount = budgetForCategory.getBudget(); - System.out.println("Budget for " + normalizedCategory + ": $" + budgetAmount); + System.out.println("Budget for " + category + ": $" + budgetAmount); List expensesForCategory = expenses.stream() - .filter(expense -> expense.getCategory().equalsIgnoreCase(normalizedCategory)) + .filter(expense -> expense.getCategory().equalsIgnoreCase(category)) .sorted(Comparator.comparingDouble(Expense::getAmount).reversed()) .collect(Collectors.toList()); if (expensesForCategory.isEmpty()) { - System.out.println("No expenses recorded for " + normalizedCategory); + System.out.println("No expenses recorded for " + category); return; } diff --git a/src/main/java/seedu/budgetbuddy/commons/SavingList.java b/src/main/java/seedu/budgetbuddy/commons/SavingList.java index b630e11db2..68015a4740 100644 --- a/src/main/java/seedu/budgetbuddy/commons/SavingList.java +++ b/src/main/java/seedu/budgetbuddy/commons/SavingList.java @@ -14,6 +14,7 @@ public class SavingList { private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + private static final double MAX_AMOUNT = 1_000_000_000_000.0; protected ArrayList savings; protected ArrayList categories; @@ -132,24 +133,52 @@ public double calculateRemainingSavings(double initialAmount, double totalExpens } public void addSaving(String category, String amount) throws BudgetBuddyException{ - if (!categories.contains(category)) { + assert category != null : "Category should not be null"; + assert amount != null : "Amount should not be null"; + LOGGER.info("Adding saving..."); + + if (categories.stream().noneMatch(existingCategory -> existingCategory.equalsIgnoreCase(category))) { throw new BudgetBuddyException("The category '" + category + "' is not listed."); } - int amountInt = Integer.parseInt(amount); - if (amountInt < 0) { - try { - throw new Exception("Savings should not be negative"); - } catch (Exception e) { - throw new RuntimeException(e); + + + if (!amount.matches("^\\d+(\\.\\d{1,2})?$")) { + throw new BudgetBuddyException("Invalid amount format. Amount should be a positive number with up" + + " to maximum two decimal places."); + } + + double amountDouble; + try { + amountDouble = Double.parseDouble(amount); + } catch (NumberFormatException e) { + throw new BudgetBuddyException("Invalid amount format. Amount should be a number."); + } + + if (amountDouble < 0) { + throw new BudgetBuddyException("Savings should not be negative."); + } + + if (amountDouble > MAX_AMOUNT) { + throw new BudgetBuddyException("Amount exceeds the maximum allowed limit of " + MAX_AMOUNT); + } + + boolean found = false; + for (Saving saving : savings) { + if (saving.getCategory().equalsIgnoreCase(category)) { + saving.setAmount(saving.getAmount() + amountDouble); + found = true; + LOGGER.info("Updated existing saving for category: " + category); + break; } } - Saving saving = new Saving(category, amountInt); - savings.add(saving); - - if (!categories.contains(category)) { - categories.add(category); + if (!found) { + Saving saving = new Saving(category, amountDouble); + savings.add(saving); } } + + + /** * Edits the saving entry at the specified index. This method updates the category and amount diff --git a/src/main/java/seedu/budgetbuddy/commons/SplitExpense.java b/src/main/java/seedu/budgetbuddy/commons/SplitExpense.java index 78c25d9fdb..8dba021d86 100644 --- a/src/main/java/seedu/budgetbuddy/commons/SplitExpense.java +++ b/src/main/java/seedu/budgetbuddy/commons/SplitExpense.java @@ -1,18 +1,34 @@ package seedu.budgetbuddy.commons; -public class SplitExpense extends Transaction{ - private final String amount; - private final String description; - private final String numberOfPeople; - - public SplitExpense(String amount, String numberOfPeople, String description) { - super("Split Expense", Double.parseDouble(amount)); - this.amount = amount; +import java.time.LocalDate; +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class SplitExpense extends Transaction { + protected String description; + private int numberOfPeople; + private LocalDate dateAdded; + + public SplitExpense(LocalDate dateAdded, double totalAmount, int numberOfPeople, String description) { + super("Shared Bill", calculateAmountPerPerson(totalAmount, numberOfPeople)); + this.dateAdded = dateAdded; this.numberOfPeople = numberOfPeople; this.description = description; } - public String getNumberOfPeople() { + public SplitExpense(double totalAmount, int numberOfPeople, String description) { + super("Shared Bill", calculateAmountPerPerson(totalAmount, numberOfPeople)); + this.numberOfPeople = numberOfPeople; + this.description = description; + this.dateAdded = LocalDate.now(); + } + + private static double calculateAmountPerPerson(double totalAmount, int numberOfPeople) { + BigDecimal amount = BigDecimal.valueOf(totalAmount); + return amount.divide(BigDecimal.valueOf(numberOfPeople), 2, RoundingMode.HALF_UP).doubleValue(); + } + + public int getNumberOfPeople() { return numberOfPeople; } @@ -20,20 +36,26 @@ public String getDescription() { return description; } - public double calculateAmountPerPerson() { - double amountValue = Double.parseDouble(amount); - double numberOfPeopleValue = Double.parseDouble(numberOfPeople); - return amountValue / numberOfPeopleValue; + public LocalDate getDateAdded() { + return dateAdded; } - + public Boolean isExpenseSettled() { return false; } + public double getAmount() { + return amount; + } + @Override public String toString() { - return "Number of People: " + numberOfPeople + " Amount: " + amount + " Description: " + - description + " Amount per person: " + calculateAmountPerPerson(); + return "Number of People: " + numberOfPeople + " Amount per person: " + amount + " Description: " + + description + " Total Amount: " + getTotalAmount(); + } + + public double getTotalAmount() { + return amount * numberOfPeople; } } diff --git a/src/main/java/seedu/budgetbuddy/commons/SplitExpenseList.java b/src/main/java/seedu/budgetbuddy/commons/SplitExpenseList.java index ee1d96dc7e..7c6f523280 100644 --- a/src/main/java/seedu/budgetbuddy/commons/SplitExpenseList.java +++ b/src/main/java/seedu/budgetbuddy/commons/SplitExpenseList.java @@ -1,17 +1,21 @@ package seedu.budgetbuddy.commons; + import java.util.ArrayList; import java.util.List; +import seedu.budgetbuddy.Ui; import seedu.budgetbuddy.exception.BudgetBuddyException; import java.util.logging.Level; import java.util.logging.Logger; public class SplitExpenseList { - private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); protected ArrayList splitexpenses; + + Ui ui = new Ui(); + public SplitExpenseList(ArrayList splitexpenses){ this.splitexpenses = splitexpenses; } @@ -33,11 +37,18 @@ public SplitExpense getSplitExpenseListAtListNumber(int listNumber) { return splitexpenses.get(listNumberAsArrayPosition); } + /** + * Lists all the expenses in the list + */ + public void listSplitExpenses() { LOGGER.info("Listing splitexpenses..."); try { - System.out.println("Split Expenses: "); + ui.printDivider(); + System.out.println(String.format("Current Currency: %s\n", DefaultCurrency.getDefaultCurrency())); + + System.out.println("Shared Bills: "); for (int i = 0; i < splitexpenses.size(); i++) { SplitExpense splitexpense = splitexpenses.get(i); @@ -46,12 +57,11 @@ public void listSplitExpenses() { continue; } System.out.print(i+1 + " | "); - System.out.print("Amount: " + splitexpense.getAmount()); - System.out.print(" Number of People: " + splitexpense.getNumberOfPeople()); System.out.print(" Description: " + splitexpense.getDescription()); - System.out.println(" Amount per person: " + splitexpense.calculateAmountPerPerson()); + System.out.print(" Number of People: " + splitexpense.getNumberOfPeople()); + System.out.print(" Amount: $" + String.format("%.2f", splitexpense.getAmount()) + "\n"); } - System.out.println("-----------------------------------------------------------------------------"); + ui.printDivider(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "An error occurred while listing expenses.", e); @@ -64,6 +74,7 @@ public void addSplitExpense(String amount, String numberOfPeople, String descrip LOGGER.info("Adding split expense..."); double amountDouble; + int numberOfPeopleInt; try{ amountDouble = Double.parseDouble(amount); } catch (NumberFormatException e) { @@ -75,7 +86,7 @@ public void addSplitExpense(String amount, String numberOfPeople, String descrip } try { - Integer.parseInt(numberOfPeople); + numberOfPeopleInt = Integer.parseInt(numberOfPeople); if (Integer.parseInt(numberOfPeople) < 0) { throw new BudgetBuddyException("Number of people should be a positive number"); } @@ -83,7 +94,7 @@ public void addSplitExpense(String amount, String numberOfPeople, String descrip throw new BudgetBuddyException("Number of people should be a number"); } - SplitExpense splitexpense = new SplitExpense(amount, numberOfPeople, description); + SplitExpense splitexpense = new SplitExpense(amountDouble, numberOfPeopleInt, description); splitexpenses.add(splitexpense); } diff --git a/src/test/java/seedu/budgetbuddy/ChangeCurrencyCommandCreatorTest.java b/src/test/java/seedu/budgetbuddy/ChangeCurrencyCommandCreatorTest.java index 1fd4f7e56a..02efdbe0c5 100644 --- a/src/test/java/seedu/budgetbuddy/ChangeCurrencyCommandCreatorTest.java +++ b/src/test/java/seedu/budgetbuddy/ChangeCurrencyCommandCreatorTest.java @@ -6,6 +6,7 @@ import seedu.budgetbuddy.commandcreator.ChangeCurrencyCommandCreator; import seedu.budgetbuddy.commons.CurrencyConverter; import seedu.budgetbuddy.commons.ExpenseList; +import seedu.budgetbuddy.commons. SplitExpenseList; import seedu.budgetbuddy.commons.RecurringExpenseLists; import seedu.budgetbuddy.commons.SavingList; import seedu.budgetbuddy.exception.BudgetBuddyException; @@ -20,6 +21,7 @@ public class ChangeCurrencyCommandCreatorTest { public void handleChangeCurrencyCommand_changeCurrencyToUSD_success() throws BudgetBuddyException { SavingList savingList = new SavingList(); ExpenseList expenseList = new ExpenseList(); + SplitExpenseList splitExpenseList = new SplitExpenseList(); RecurringExpenseLists recurringExpenseLists = new RecurringExpenseLists(); CurrencyConverter currencyConverter = new CurrencyConverter(); @@ -28,10 +30,10 @@ public void handleChangeCurrencyCommand_changeCurrencyToUSD_success() throws Bud String input = "change currency USD"; ChangeCurrencyCommandCreator changeCurrencyCommandCreator = new ChangeCurrencyCommandCreator(input, savingList, - expenseList, recurringExpenseLists, currencyConverter); + expenseList, splitExpenseList, recurringExpenseLists, currencyConverter); Command command = changeCurrencyCommandCreator.handleChangeCurrencyCommand(input, savingList, - expenseList, recurringExpenseLists, currencyConverter); + expenseList, splitExpenseList, recurringExpenseLists, currencyConverter); assertEquals(ChangeCurrencyCommand.class, command.getClass()); } @@ -40,6 +42,7 @@ public void handleChangeCurrencyCommand_changeCurrencyToUSD_success() throws Bud public void handleChangeCurrencyCommand_changeCurrency_invalidCurrencyCode() throws BudgetBuddyException { SavingList savingList = new SavingList(); ExpenseList expenseList = new ExpenseList(); + SplitExpenseList splitExpenseList = new SplitExpenseList(); RecurringExpenseLists recurringExpenseLists = new RecurringExpenseLists(); CurrencyConverter currencyConverter = new CurrencyConverter(); @@ -47,10 +50,10 @@ public void handleChangeCurrencyCommand_changeCurrency_invalidCurrencyCode() thr String input = "change currency abc"; ChangeCurrencyCommandCreator changeCurrencyCommandCreator = new ChangeCurrencyCommandCreator(input, savingList, - expenseList, recurringExpenseLists, currencyConverter); + expenseList, splitExpenseList, recurringExpenseLists, currencyConverter); Command command = changeCurrencyCommandCreator.handleChangeCurrencyCommand(input, savingList, - expenseList, recurringExpenseLists, currencyConverter); + expenseList, splitExpenseList, recurringExpenseLists, currencyConverter); assertNull(command); } @@ -58,6 +61,7 @@ public void handleChangeCurrencyCommand_changeCurrency_invalidCurrencyCode() thr public void handleChangeCurrencyCommand_changeCurrency_invalidCommandFormat() throws BudgetBuddyException { SavingList savingList = new SavingList(); ExpenseList expenseList = new ExpenseList(); + SplitExpenseList splitExpenseList = new SplitExpenseList(); RecurringExpenseLists recurringExpenseLists = new RecurringExpenseLists(); CurrencyConverter currencyConverter = new CurrencyConverter(); @@ -65,10 +69,10 @@ public void handleChangeCurrencyCommand_changeCurrency_invalidCommandFormat() th String input = "change currency abc asd"; ChangeCurrencyCommandCreator changeCurrencyCommandCreator = new ChangeCurrencyCommandCreator(input, savingList, - expenseList, recurringExpenseLists, currencyConverter); + expenseList, splitExpenseList, recurringExpenseLists, currencyConverter); Command command = changeCurrencyCommandCreator.handleChangeCurrencyCommand(input, savingList, - expenseList, recurringExpenseLists, currencyConverter); + expenseList, splitExpenseList, recurringExpenseLists, currencyConverter); assertNull(command); } } diff --git a/src/test/java/seedu/budgetbuddy/ExpenseListTest.java b/src/test/java/seedu/budgetbuddy/ExpenseListTest.java index 164f14e949..740d9940ef 100644 --- a/src/test/java/seedu/budgetbuddy/ExpenseListTest.java +++ b/src/test/java/seedu/budgetbuddy/ExpenseListTest.java @@ -41,17 +41,59 @@ public void addExpense_addingExpense_success() throws BudgetBuddyException { assertEquals("Bus Fare", expenseList.getExpenses().get(0).getDescription()); } - @Test @Disabled + @Test public void addExpense_addingNegativeExpense_exceptionThrown() { ExpenseList expenseList = new ExpenseList(); try { expenseList.addExpense("Transport", "-50", "Bus Fare"); fail(); } catch (Exception e) { - assertEquals("java.lang.Exception: Expenses should not be negative", e.getMessage()); + assertEquals("Invalid amount format. Amount should be a positive number with up to maximum two decimal " + + "places.", e.getMessage()); + } + } + + @Test + public void addExpense_addingInvalidAmount_exceptionThrown() { + ExpenseList expenseList = new ExpenseList(); + try { + expenseList.addExpense("Transport", "abc", "Bus Fare"); + fail(); + } catch (Exception e) { + assertEquals("Invalid amount format. Amount should be a positive number with up to maximum two decimal " + + "places.", e.getMessage()); } } + @Test + public void addExpense_addingNullCategory_exceptionThrown() { + ExpenseList expenseList = new ExpenseList(); + try { + expenseList.addExpense("abc", "50", "Bus Fare"); + fail(); + } catch (Exception e) { + assertEquals("The category 'abc' is not listed.", e.getMessage()); + } + } + + @Test + public void deleteExpense_validInput_success() throws BudgetBuddyException { + // Create an ExpenseList and add two expenses + ExpenseList expenseList = new ExpenseList(); + expenseList.addExpense("Transport", "50", "Bus Fare"); + expenseList.addExpense("Housing", "30", "Lunch"); + + // Delete the first expense + expenseList.deleteExpense(1); + + // Assert: Check if the first expense is deleted + assertEquals(1, expenseList.getExpenses().size()); + assertEquals("Transport", expenseList.getExpenses().get(0).getCategory()); + assertEquals(50, expenseList.getExpenses().get(0).getAmount(), 0.01); // using delta for double comparison + assertEquals("Bus Fare", expenseList.getExpenses().get(0).getDescription()); + } + + //@@ jasraa @Test public void editExpense_validInput_success() throws BudgetBuddyException { @@ -133,7 +175,7 @@ public void filterExpenses_filterByMinAmount_returnsThreeMatches() throws Budget } - @Test + @Test public void filterExpenses_filterByMaxAmount_returnsOneMatches() throws BudgetBuddyException { ExpenseList expenses = new ExpenseList(); expenses.addExpense("Groceries", "100", "Apples"); @@ -141,7 +183,7 @@ public void filterExpenses_filterByMaxAmount_returnsOneMatches() throws BudgetBu expenses.addExpense("Entertainment", "75", "Movie"); expenses.addExpense("Groceries", "100", "apple"); ArrayList filteredExpenses = expenses.filterExpenses("" - , null, 75.00); + , null, 76.00); assertEquals(2, filteredExpenses.size()); @@ -155,7 +197,7 @@ public void filterExpenses_filterByRange_returnsTwoMatches() throws BudgetBuddyE expenses.addExpense("Entertainment", "75", "Movie"); expenses.addExpense("Groceries", "100", "apple"); ArrayList filteredExpenses = expenses.filterExpenses("" - , 49.00, 76.00); + , 49.00, 75.00); assertEquals(2, filteredExpenses.size()); diff --git a/src/test/java/seedu/budgetbuddy/SavingListTest.java b/src/test/java/seedu/budgetbuddy/SavingListTest.java index 553bda9943..c2c83dcff6 100644 --- a/src/test/java/seedu/budgetbuddy/SavingListTest.java +++ b/src/test/java/seedu/budgetbuddy/SavingListTest.java @@ -16,6 +16,49 @@ public class SavingListTest { private static final Logger LOGGER = Logger.getLogger(SavingListTest.class.getName()); + + @Test + public void addSaving_validInput_success() throws BudgetBuddyException { + SavingList savingList = new SavingList(); + savingList.addSaving("Salary", "500"); + + assertEquals(1, savingList.getSavings().size()); + assertEquals("Salary", savingList.getSavings().get(0).getCategory()); + assertEquals(500, savingList.getSavings().get(0).getAmount()); + } + + @Test + public void addSaving_invalidAmount_exceptionThrown() { + SavingList savingList = new SavingList(); + try { + savingList.addSaving("Salary", "abc"); + } catch (BudgetBuddyException e) { + assertEquals("Invalid amount format. Amount should be a positive number with up to maximum two decimal " + + "places.", e.getMessage()); + } + } + + @Test + public void addSaving_negativeAmount_exceptionThrown() { + SavingList savingList = new SavingList(); + try { + savingList.addSaving("Salary", "-1.00"); + } catch (BudgetBuddyException e) { + assertEquals("Invalid amount format. Amount should be a positive number with up to maximum "+ + "two decimal places.", e.getMessage()); + } + } + + @Test + public void addSaving_nullCategory_exceptionThrown() { + SavingList savingList = new SavingList(); + try { + savingList.addSaving("abc", "500"); + } catch (BudgetBuddyException e) { + assertEquals("The category 'abc' is not listed.", e.getMessage()); + } + } + @Test public void calculateRemainingSavings_sufficientFunds_success() { SavingList savingList = new SavingList(); diff --git a/src/test/java/seedu/budgetbuddy/SplitExpenseListTest.java b/src/test/java/seedu/budgetbuddy/SplitExpenseListTest.java index c5cdf872b2..8d4063f403 100644 --- a/src/test/java/seedu/budgetbuddy/SplitExpenseListTest.java +++ b/src/test/java/seedu/budgetbuddy/SplitExpenseListTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import seedu.budgetbuddy.commons.SplitExpenseList; @@ -20,7 +19,7 @@ public void addSplitExpense_addingsplitexpense_success() throws BudgetBuddyExcep splitExpenseList.addSplitExpense("12", "12", "Lunch"); assertEquals(1, splitExpenseList.getSplitExpenses().size()); - assertEquals("12", splitExpenseList.getSplitExpenses().get(0).getNumberOfPeople()); + assertEquals(12, splitExpenseList.getSplitExpenses().get(0).getNumberOfPeople()); assertEquals("Lunch", splitExpenseList.getSplitExpenses().get(0).getDescription()); } @@ -46,17 +45,6 @@ public void addSplitExpense_invalidNumberOfPeople_exceptionThrown() throws Budge } } - @Test @Disabled - public void addSplitExpense_nullDescription_exceptionThrown() throws BudgetBuddyException{ - SplitExpenseList splitExpenseList = new SplitExpenseList(); - try { - splitExpenseList.addSplitExpense("12", "12", null); - fail(); - } catch (BudgetBuddyException e) { - assertEquals("Description should not be null", e.getMessage()); - } - } - @Test public void addSplitExpense_negativeAmount_exceptionThrown() throws BudgetBuddyException{ SplitExpenseList splitExpenseList = new SplitExpenseList();