diff --git a/.gitignore b/.gitignore index 2873e189e1..dddfef5be0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +/logs/ diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..ab58064db9 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.budgetbuddy.BudgetBuddy + diff --git a/README.md b/README.md index e243ece764..d3bbfa031d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Duke project template +# BudgetBuddy -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. +BudgetBuddy is a Command Line Interface (CLI) desktop application designed to help users manage their finances efficiently. +With BudgetBuddy, users can categorize expenses and income, add and deduct budgets, visualize income and expenses +through graphs, manage savings, and store data persistently. Given below are instructions on how to use it. ## Setting up in Intellij @@ -8,21 +10,16 @@ Prerequisites: JDK 17 (use the exact version), update Intellij to the most recen 1. **Ensure Intellij JDK 17 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 17 in a previous Intellij project. 1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -1. **Verify the setup**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: +1. **Verify the setup**: After the importing is complete, locate the `src/main/java/seedu/budgetbuddy/BudgetBuddy.java` file, right-click it, and choose `Run BudgetBuddy.main()`. If the setup is correct, you should see something like the below: ``` > Task :compileJava > Task :processResources NO-SOURCE > Task :classes - - > Task :Duke.main() - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - - What is your name? + + > Task :seedu.budgetbuddy.BudgetBuddy.main() + ======================================================== + Welcome to Budget Buddy! + ======================================================== ``` Type some word and press enter to let the execution proceed to the end. @@ -39,7 +36,7 @@ Prerequisites: JDK 17 (use the exact version), update Intellij to the most recen ### JUnit tests -* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template. +* A skeleton JUnit test (`src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java`) is provided with this project template. * If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html). ## Checkstyle @@ -55,7 +52,7 @@ The project uses [GitHub actions](https://github.com/features/actions) for CI. W `/docs` folder contains a skeleton version of the project documentation. -Steps for publishing documentation to the public: +Steps for publishing documentation to the public: 1. If you are using this project template for an individual project, go your fork on GitHub.
If you are using this project template for a team project, go to the team fork on GitHub. 1. Click on the `settings` tab. diff --git a/build.gradle b/build.gradle index ea82051fab..5aa26f896c 100644 --- a/build.gradle +++ b/build.gradle @@ -12,10 +12,12 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + implementation 'org.knowm.xchart:xchart:3.8.8' } test { useJUnitPlatform() + jvmArgs '-ea' testLogging { events "passed", "skipped", "failed" @@ -29,11 +31,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("seedu.budgetbuddy.BudgetBuddy") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("budgetbuddy") archiveClassifier.set("") } @@ -43,4 +45,6 @@ checkstyle { run{ standardInput = System.in + jvmArgs '-ea' + enableAssertions = true } diff --git a/data/BudgetBuddy.txt b/data/BudgetBuddy.txt new file mode 100644 index 0000000000..2351cc432a --- /dev/null +++ b/data/BudgetBuddy.txt @@ -0,0 +1,30 @@ +expense | plane | 1000.0 | 15/9/2024 | FOOD +expense | hello | 2.0 | 15/9/2024 | OTHERS +expense | go skiing | 100.0 | 15/10/2024 | FOOD +expense | plane tickets to korea | 1000.0 | 15/9/2024 | OTHERS +expense | zoo tickets | 20.0 | 15/9/2024 | FOOD +expense | plane tickets to japan | 1000.0 | 15/10/2024 | OTHERS +expense | plane tickets to europe | 1000.0 | 1/8/2024 | OTHERS +expense | wingstop | 20.0 | 15/10/2024 | OTHERS +expense | plane ticket to japan | 1000.0 | 10/10/2024 | TRANSPORT +expense | food | 5.5 | 10/10/2024 | FOOD +expense | test | 10.0 | 10/10/2024 | OTHERS +expense | popmart | 300.0 | 20/10/2023 | ENTERTAINMENT +expense | table | 123.0 | 1/10/2023 | OTHERS +expense | char | 2567.0 | 3/10/2023 | OTHERS +expense | fly kite | 50.0 | 9/10/2025 | ENTERTAINMENT +expense | go zoo | 300.0 | 10/10/2024 | OTHERS +expense | restaurant | 300.0 | 10/10/2025 | FOOD +income | tuition fees | 1000.0 | 15/9/2024 +income | tuition | 2.0E8 | 15/9/2024 +income | tuition fee | 10000.0 | 15/10/2024 +income | Starbucks crew | 1000.0 | 1/10/2024 +income | Mcdonald crew | 1000.0 | 15/10/2024 +income | fries crew | 2000.0 | 15/10/2023 +income | tuition fee | 1000.0 | 15/9/2024 +budget | 3200.0 | 2024-11 | {ENTERTAINMENT=3000.0, FOOD=200.0} +budget | 2400.0 | 2024-10 | {TRANSPORT=1200.0, ENTERTAINMENT=1200.0} +budget | 2550.0 | 2024-08 | {OTHERS=1800.0, FOOD=750.0} +budget | 1700.0 | 2024-07 | {OTHERS=1700.0} +budget | 3900.0 | 2024-05 | {OTHERS=1300.0, ENTERTAINMENT=1300.0, FOOD=1300.0} +budget | 2600.0 | 2023-08 | {OTHERS=1900.0, UTILITIES=700.0} diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..55947a432d 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +| Display | Name | Github Profile | Portfolio | +|--------------------------------------|--------------|-------------------------------------------|-----------------------------------| +| ![](images/Portrait_placeholder.png) | Kenneth Ang | [Github](https://github.com/Chinorea) | [Portfolio](team/chinorea.md) | +| ![](images/Portrait_placeholder.png) | Telfer Ang | [GitHub](https://github.com/telferang) | [Portfolio](team/telferang.md) | +| ![](images/Portrait_placeholder.png) | Cleon | [GitHub](https://github.com/chuacleon) | [Portfolio](team/chuacleon.md) | +| ![](images/Portrait_placeholder.png) | Zilia Ang | [Github](https://github.com/ZiliaAJY) | [Portfolio](team/ziliaajy.md) | +| ![](images/Portrait_placeholder.png) | Alfred Goh | [Github](https://github.com/Alfred-Goh02) | [Portfolio](team/alfred-goh02.md) | diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..9db3eb80e5 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,989 @@ # Developer Guide +--- + +## Table of Contents + +--- + +[1. Introduction](#1-introduction)
+[2. Setup Guide](#2-setup-guide)
+  [2.1 Prerequisites](#21-prerequisites)
+[3. Design](#3-design)
+  [3.1 Architecture](#31-architecture)
+  [3.2 Parser Class](#32-parser-class)
+  [3.3 UI Class](#33-ui-class)
+  [3.4 Command Class](#34-command-class)
+  [3.5 Validator Classes](#35-validator-classes)
+  [3.6 Expense and Income Class](#36-expense-expensemanager-income-and-incomemanager-class)
+[4. Implementation](#4-implementation)
+  [4.1 Expense Features](#41-expense-features)
+   [4.1.1 Add Expense](#411-add-expense-feature)
+   [4.1.2 Delete Expense](#412-delete-expense-feature)
+   [4.1.3 Search Expense](#413-search-expense-feature)
+   [4.1.4 Breakdown Expenses](#414-breakdown-expense-feature)
+   [4.1.5 Display Monthly Expenses](#415-display-monthly-expenses)
+   [4.1.6 Display Expenses for Month with Categories](#416-display-expenses-for-month-with-categories)
+   [4.1.7 List Expense](#417-list-expenses-feature)
+   [4.1.8 Edit Expense](#418-edit-expense-feature)
+  [4.2 Income Features](#42-income-features)
+   [4.2.1 Add Income](#421-add-income-feature)
+   [4.2.2 Delete Income](#422-delete-income-feature)
+   [4.2.3 List Expense](#423-list-income-feature)
+   [4.2.4 Edit Expense](#424-edit-income-feature)
+  [4.3 Budget Features](#43-budget-features)
+   [4.3.1 Add Budget](#431-add-budget-feature)
+   [4.3.2 Deduct Budget](#432-deduct-budget-feature)
+   [4.3.3 List Budget](#433-list-budget-feature)
+   [4.3.4 List Remaining Budget](#434-list-remaining-budget-feature)
+  [4.4 Savings Features](#44-savings-features)
+   [4.4.1 Display Savings](#441-display-savings-feature)
+  [4.5 Income Spent Features](#45-income-spent-features)
+   [4.5.1 Display Income Spent](#451-display-income-spent-feature)
+  [4.6 Miscellaneous Features](#46-miscellaneous-features)
+   [4.6.1 Display Help](#461-display-help-feature)
+[5. Appendix](#5-appendix)
+  [5.1 Product Scope](#51-product-scope)
+  [5.2 User Stories](#52-user-stories)
+  [5.3 Non-Functional Requirements](#53-non-functional-requirements)
+  [5.4 Glossary](#54-glossary)
+  [5.5 Instruction for Manual Testing](#55-instructions-for-manual-testing)
+ + + ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +--- + +All UML diagrams within the Developer Guide have been created on [Draw.io](https://draw.io/) + + +## 1. Introduction + +--- + +BudgetBuddy is a CLI-based expense, income and budget tracking application. BudgetBuddy is designed to help users +manage and monitor their daily and monthly expenses. The system tracks various categories of expenses and allows users +to receive insights on their budget and spending patterns. + +## 2. Setup Guide + +--- + +This section describes how to setup the coding environment, along with the tools needed to work on BudgetBuddy + +### 2.1 Prerequisites +1. JDK 17 +2. Any working IDE that supports Java (Intellij IDEA preferred) + +## 3. Design + +--- + +### 3.1 Architecture +The following activity diagram shows the rough overview of BudgetBuddy + +![ApplicationArchitecture.drawio.png](diagrams/ApplicationArchitecture.drawio.png) + +`BudgetBuddy` is the main class of application which the user can interact with directly. The input from the user is +processed by the main class and passed to the `Parser`. `Parser` will check for any valid keywords in the input using +the `isCommand` method of all the `Command` object. Once the keywords are present, it will pass the input to a +`Validator` object which validates the command on its formatting and details. Depending on the result of the `Validator` +, the command will be executed in `BudgetBuddy`. The `Validator` and `Object` genre classes utilizes methods and classes +present in the transaction folder. + +### 3.2 Parser Class +The `Parser` class is to mainly determine whether the user input is valid, and proceed to process the command after. +It uses Boolean Methods to determine the presence of keywords, and then creates a Validator class to process the command +should the keywords be present. + +The following are some examples: + +| Boolean Methods | Check if input starts with | Feature Requires | +|------------------------------------------|----------------------------|------------------| +| `ListMonthlyExpensesCommand.isCommand()` | list monthly expenses | input | +| `AddExpenseCommand.isCommand()` | add expense | input | + +The Parser class is also used to parse lines from the `Storage` .txt file and loads it into the main application upon +restart. This will be covered more under the storage class. + +### 3.3 UI Class + +The UI Class is to print out elements of the app in the CLI. It contains many methods used to print general, often-used +messages such as displayWelcomeMessage() and displayExitMessage(). + +### 3.4 Command Class + +The `Command` class is a parent class which is responsible for checking for input keywords. It has multiple subclasses, +which corresponds to a specific function of the application. Each of its subclass checks for specific keywords in the +user input. If the input is valid as checked by the `isCommand()` method, it will call its corresponding validator. + +### 3.5 Validator Classes + +The Validator classes are a group of classes which is responsible for further checks on input, such as whether it has +the right formatting and contains all necessary details. It will return different subclasses of the command object +depending on whether the input is valid or not. Once the object is instantiated and returned, the `main` class will call +`execute` to execute the feature. + +The following sequence diagram shows the process of what happens when a user input is passed through the application, +until it gets executed. + +**Note**: +1. Validator class is not a parent class, but we have grouped it under validators to prevent cluttering of +the diagrams. +2. Validator also returns different type of Command Classes depending on the validity of input, which has been omitted +for the same reason as above. + +![CommandCreation.drawio.png](diagrams/CommandCreation.drawio.png) + +#### 3.5.1 Edit Validator Classes + +The Edit Validator classes are specialised for EditIncome and EditExpense commands as they take in 2 sets of input from +the user. This first set of input command follows validators classes at [3.5 Validator Classes](#35-validator-classes). +Upon calling of execution of command, a second set of input will be retrieved from the user, where it checks if the second +set of inputs are valid, and it returns a `boolean` object based on the second input validity. + +The following sequence diagram shows the process of what happens from the function call of `execute()` from main until +the return of `boolean` object from validator class. + +![EditCommandCreation.drawio.png](diagrams/EditSequenceDiagram.drawio.png) + +### 3.6 Expense, ExpenseManager, Income and IncomeManager Class +The `Expense` and `Income` class inherits from the Transaction class. + +`Expense` class stores one expense record given by the user. + +`ExpenseManager` class stores a list of `Expense`. + +`Income` class stores one income record given by the user. + +`IncomeManager` class stores a list of `Income`. + +The methods are not shown. + +![ExpenseAndIncomeClassDiagram.drawio.png](diagrams/ExpenseAndIncomeClassDiagram.drawio.png) + +## 4. Implementation + +--- +### 4.1 Expense Features + +#### 4.1.1 Add Expense Feature +The Add Expense feature enables users to add expenses for different categories. This functionality is controlled by the +AddExpenseCommand class, which is produced by the Parser class based on user input. The AddExpenseCommand class uses an +AddExpenseValidator class to validate the provided description, amount, category, and date, and then create and add the +given information to `AddExpenseCommand` if valid. Below is the relevance of these attributes: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|--------------------------------------| +| description | String | The short description of the expense | +| amount | double | The expense amount to be added | +| category | Category | The category of expense to be added | +| date | LocalDate | The date of the expense | + +The BudgetBuddy class then calls the `execute()` method of the `AddExpenseCommand` object which uses the following +method in the `ExpenseManager` class to add the expense: + +| Method | Return Type | Relevance | +|---------------------|-------------|-------------------------------------------| +| `addExpense(expense)` | void | Add new expense to the list of `expenses` | + +A `RemainingBudgetManager` object will be created to find the budget remaining for the given month and category. +Finally, the acknowledgement message along with the budget remaining is displayed to the user using the `Ui` class +`displayToUser()` method. + +The following UML Sequence diagram shows the Add Expense Feature. +We assume that the `AddExpenseCommand` has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![AddExpenseSequenceDiagram.drawio.png](diagrams/AddExpenseSequenceDiagram.drawio.png) + +#### 4.1.2 Delete Expense Feature +The Delete Expense feature enables users to delete expense in the list of expenses. This functionality is controlled by +the DeleteExpenseCommand class, which is produced by the Parser class based on user input. The DeleteExpenseCommand +class uses DeleteExpenseValidator validate the provided index is valid and within the number of expenses. Next, the +index will be stored in the newly created `DeleteExpenseCommand`. Below is the relevance of these attributes: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|--------------------------------| +| index | int | index of the expense to delete | + +The BudgetBuddy class then calls the `execute()` method of the `DeleteExpenseCommand` object which uses the following +method in the `ExpenseManager` class to add the expense: + +| Method | Return Type | Relevance | +|----------------------|-------------|--------------------------------------------------| +| `deleteExpense(index)` | void | Delete given expense from the list of `expenses` | + +A `RemainingBudgetManager` object will be created to find the budget remaining for the given month and category. +Finally, the acknowledgement message along with the budget remaining is displayed to the user using the `Ui` class +`displayToUser()` method. + +The following UML Sequence diagram shows the Delete Expense Feature. +We assume that the `DeleteExpenseCommand` has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![DeleteExpenseSequenceDiagram.drawio.png](diagrams/DeleteExpenseSequenceDiagram.drawio.png) + +#### 4.1.3 Search Expense Feature +The Search Expense Feature enables users to search for specific expenses based on a description provided by the +user. This feature is managed by the `SearchExpensesCommand` class, initialized by the `Parser` class, with the help +of a helper class `SearchExpenseValidator` to validate and extract the user description. +The `SearchExpensesCommand` object is then created with the keyword as an attribute. The class attributes and their +relevance is as follows: + +|Variable Name| Variable Type | Relevance | +|-------------|---------------|-----------| +|keyword| String | Description to find in expenses| + +The BudgetBuddy class then calls the `execute()` method of the `SearchExpenseCommand` object which uses the +`searchExpenses()` method in the `ExpenseManager` class, displaying the result to the user using the `Ui` class +`displayToUser()` method. + +Below is a sequence diagram representing the execution of the Search Expense interaction. +We assume that the `SearchExpenseCommand` has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![SearchExpenseSequenceDiagram.drawio.png](diagrams/SearchExpenseSequenceDiagram.drawio-3.png) + +Process Overview: +1. The user issues a command to search for a specific expense i.e. `search expense Japan`. BudgetBuddy parses this + input with the help of the `Parser` class. +2. The `Parser` calls the `isCommand()` method of the `SearchExpenseCommand` class, to check if the user input + starts with "search expense". +3. If the user input starts with "search expense", the `Parser` then calls the `processCommand()` method of + a helper class, `SearchExpenseValidator` to extract the description to be filtered on. +4. The `processCommand()` method above returns a new `SearchExpenseCommand` object initialized with the description + extracted as the `keyword` attribute. +5. BudgetBuddy then calls the `execute()` method of the `SearchExpenseCommand` object. +6. If the keyword attribute is an empty string, the `SearchExpenseCommand` object calls the `searchEmptyMessage()` + method of the `Ui` class, displaying an error message to the user that no descriptor was provided. +7. Else, the `SearchExpenseCommand` object calls the `searchExpenses()` method of the `ExpenseManager` class, + filtering the `expenses` ArrayList and returning a String containing all expenses with the given descriptor + in the description of the expenses. The `SearchExpenseCommand` object then calls the `displayToUser()` method in `Ui`, + displaying this String to the user. + +#### 4.1.4 Breakdown Expense Feature +The Breakdown Expense Feature allows users to gain greater insights into their expense history. The feature +makes use of the user's expense history, breaking down their total expense into the different categories, displaying +this result to the user. This allows the user to know where they are spending their money. This feature differs from +the List Expense Feature in that this feature gives a quick overview of a user's expenditure, in terms of categories. + +This feature is managed by the `BreakdownExpensesCommand` class, initialized by the `Parser` class upon user input. The +`BudgetBuddy` class then calls the `execute()` method of the `BreakdownExpensesCommand` object, which uses the +`breakdownExpensesByCategory()` method in the `ExpenseManager` class to execute the functionality, displaying +the result via the `displayToUser()` method in the `Ui` class. + +Below is the sequence diagram for the execution of the Breakdown Expense Feature interaction. +We assume that the `BreakdownExpensesCommand` has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![BreakdownExpenseSequenceDiagram.drawio.png](diagrams/BreakdownExpenseSequenceDiagram.drawio.png) + + +#### 4.1.5 Display Monthly Expenses +The Display Monthly Expenses feature displays an XY-Chart which helps shows user's monthly expenses across the specified +year. This functionality is controlled by the `DisplayTotalExpensesCommand` class which uses +`DisplayTotalExpensesValidator` class to validate the command string and year, and performs the deduction if valid. +Below shows the relevance of the attribute: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|--------------------------------------------------| +| year | int | The specified year of the chart to be displayed | + +The `BudgetBuddy` class then calls `execute()` method of `DisplayTotalExpensesCommand` object which uses the +`displayExpensesOverMonthGraph(int year)` method in `ExpenseManager` class to display the chart. + +The following methods in `ExpenseOverMonthGraph` class is called by `displayExpensesOverMonth(int year)` method to +display the chart from XChart. + +| Method | Return Type | Relevance | +|--------------------------------------------------------------------|--------------------------|-------------------------------------------------------------------------------------------------------------------| +| `monthMapBuilder(ArrayList expense)` | `Map` | Builds a Hashmap of with each key being months and each value being the total expenses of the corresponding month | +| `chartPrinter(Map monthlyExpenseMap, int year)` | `void` | Prints the XY-Chart of the specified year | + +Finally, the XChart library will be called to build the chart and display it with SwingWrapper. + +The following UML Sequence diagram shows how the command is executed for this feature.
+We assume that the command for this feature has already been created and returned to `BudgetBuddy`.
+The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + + + +![DisplayMonthlyExpenses.drawio.png](diagrams/DisplayMonthlyExpenses.drawio.png) + + +#### 4.1.6 Display Expenses for Month with Categories +The Display Expenses for Month with Categories displays a pie chart which helps shows user's expenses weightage for each +category for the specified month. This functionality is controlled by +`DisplayExpensesForMonthWithCategoriesGraphCommand` class which uses the +`DisplayExpensesForMonthWithCategoriesGraphValidator` class to validate the command string and date in YearMonth format, +and performs the deduction if valid. Below shows the relevance of the attribute. + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|-----------------------------------------------------------| +| yearMonth | YearMonth | The specified year and month of the chart to be displayed | + + +The `BudgetBuddy` class then calls `execute()` method of `DisplayExpensesForMonthWithCategoriesGraphCommand` object +which uses the `displayExpensesForMonthWithCategoriesGraph(YearMonth yearMonth)` method in `ExpenseManager` class to +display the chart. + +The following methods in `ExpensesCategoryPieChart` class is called by +`displayExpensesForMonthWithCategoriesGraph(YearMonth yearMonth)` method to display the chart from XChart. + +| Method | Return Type | Relevance | +|-------------------------------------------------------------------------------------------------------|-------------------------|--------------------------------------------------------------------------------------------| +| `expensesByCategoryMapBuilder(YearMonth yearMonth)` | `Map` | Builds a Hashmap with key being category and value is the total expenses for each category | +| `getTotalExpensesForMonthWithCategories(YearMonth yearMonth, Category category)` | `double` | Helps to calculate the total expenses for the specified month given a category | +| `displayExpenseByCategoryPieChart(YearMonth yearMonth, Map expensesByCategoryMap)` | `void` | Prints the Pie Chart of the specified month and year | + +Finally, the XChart library will be called to build the chart and display it with SwingWrapper. + +The following UML Sequence diagram shows how the command is executed for this feature.
+We assume that the command for this feature has already been created and returned to `BudgetBuddy`.
+The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![DisplayExpensesWithCategories.drawio.png](diagrams/DisplayExpensesWithCategories.drawio.png) + +#### 4.1.7 List Expenses Feature +The List Expense feature enables users to view saved expenses in the application. Additionally, user may add additional +filters to display only desired categories and months. The total expense amount based on the displayed expenses will be +summed and displayed to the user. This feature is controlled by the `ListExpenseCommand` class, where it is initialized +by the `Parser` class. The `Parser` class uses `ListExpenseValidator` to check and extract the filter field that +the user input. The `ListExpenseCommand` object is then created with a Category and YearMonth Attribute. + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|------------------------------------------------------| +| category | Category | The category of expenses to be listed | +| month | YearMonth | The specific month and year of expenses to be listed | + +The `BudgetBuddy` class then calls the `execute()` method of `ListExpenseCommand` object which uses the following +methods in `ExpenseManager` class to list expenses based on user input category and month. If user does not specify a +specific month or category, it will show every valid expense instead. + +| Method | Return Type | Relevance | +|----------------------------------------|-------------|--------------------------------------------------------------| +| `listExpenses()` | void | Lists out all expenses saved | +| `listExpensesWithCategory(category)` | String | Lists out all expenses saved with the specified category | +| `listExpensesWithDate(month)` | String | Lists out all expenses saved in specified month | +| `listExpensesWithCategoryAndDate(month)` | String | Lists out all expenses saved in specified month and category | + +Then, with the variations of `listExpenses()` commands in `ExpenseManager`, the list of expenses will be displayed to +the user using the `Ui` class `displayToUser()` method. + +We assume that the command for this feature has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +The following UML Sequence diagram shows how the List Expense Feature undergo its execute function. +![ListExpenseSequenceDiagram.drawio.png](diagrams/ListExpenseSequenceDiagram.drawio.png) + +#### 4.1.8 Edit Expense Feature +The Edit Expense Feature enable users to edit pre-existing entries of expenses in the application. Users are only +allowed to change the date, category and amount fields of the expense field. Currently, the description of each task +cannot be edited, however it may be implemented for future versions. There are 2 sets of instruction that the user +has to input. The first set is to identify which expense to edit, while the second is the fields to be edited. +This feature is controlled by the `EditExpenseCommand` class, where it is initialized by the `Parser` class. The +`Parser` class uses `EditExpenseValidator` to check and extract the `Expense` object based on index that the user input. +`EditExpenseCommand` is then created with the extracted `Expense` object. + +`EditExpenseCommand` class contains the following attributes, which will be used to store the values that will replace +the existing value in the Expense object. During the creation of `EditExpenseCommand`, only `expense` object will be +saved, while the other values will be initialized during subsequent function calls. + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|------------------------------------------------------------------------| +| category | Category | User input category value to replace current category value in expense | +| date | LocalDate | User input date value to replace current date value in expense | +| amount | double | User input amount value to replace current amount value in expense | +| expense | Expense | User specified Expense based on index | + +The `BudgetBuddy` class then calls the `execute()` method of `EditExpenseCommand` object which the `EditExpenseCommand` +class then uses the `Ui` class to call function `getUserEditFields()` for editing the expense parameters. The +`EditExpenseCommand` then uses `EditExpenseValidator` to check and extract for any valid fields. If valid, a +`processEdit()` function will be called to update values in the expense object. + +The following UML Sequence diagram extends froms [3.5.1 EditValidator Classes](#351-edit-validator-classes), where it +shows how the EditExpenseCommand updates the `Expenses(TransactionType)` object after validating second user input. + +![EditSequenceDiagram2.drawio.png](diagrams/EditSequenceDiagram2.drawio.png) + +### 4.2 Income Features + +#### 4.2.1 Add Income Feature +The Add Income feature enables users to add incomes. This functionality is controlled by the +AddIncomeCommand class, which is produced by the Parser class based on user input. The AddIncomeCommand class uses an +AddIncomeValidator class to validate the provided description, amount, and date, and then create and add the +given information to `AddIncomeCommand` if valid. Below is the relevance of these attributes: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|--------------------------------------| +| description | String | The short description of the expense | +| amount | double | The expense amount to be added | +| date | LocalDate | The date of the expense | + +The BudgetBuddy class then calls the `execute()` method of the `AddIncomeCommand` object which uses the following +method in the `IncomeManager` class to add the income: + +| Method | Return Type | Relevance | +|-------------------|-------------|-----------------------------------------| +| `addIncome(income)` | void | Add new income to the list of `incomes` | + +The acknowledgement message is displayed to the user using the `Ui` class `displayToUser()` method. + +The UML Sequence diagram is not shown because it is very similar to the [Add Expense Feature](#411-add-expense-feature). + + +#### 4.2.2 Delete Income Feature +The Delete Income feature enables users to delete income in the list of incomes. This functionality is controlled by +the DeleteIncomeCommand class, which is produced by the Parser class based on user input. The DeleteIncomeCommand +class uses DeleteIncomeValidator validate the provided index is valid and within the number of incomes. Next, the +index will be stored in the newly created `DeleteIncomeCommand`. Below is the relevance of these attributes: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|-------------------------------| +| index | int | index of the income to delete | + +The BudgetBuddy class then calls the `execute()` method of the `DeleteIncomeCommand` object which uses the following +method in the `IncomeManager` class to add the expense: + +| Method | Return Type | Relevance | +|---------------------|-------------|------------------------------------------------| +| `deleteIncome(index)` | void | Delete given income from the list of `incomes` | + +The acknowledgement message is displayed to the user using the `Ui` class `displayToUser()` method. + +The UML Sequence diagram is not shown because it is very similar to the [Add Income Feature](#412-delete-expense-feature). + +#### 4.2.3 List Income Feature +The List Income feature enables users to view saved income in the application. Additionally, user may add additional +filters to display only desired months. The total income amount based on the displayed income will be summed and +displayed to the user. This feature is controlled by the `ListIncomeCommand` class, where it is initialized by the +`Parser` class. The `Parser` class uses `ListIncomeValidator` to check and extract the filter field that the user +input. The `ListIncomeCommand` object is then created with a Category and YearMonth Attribute. + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|------------------------------------------------------| +| month | YearMonth | The specific month and year of expenses to be listed | + +The `BudgetBuddy` class then calls the `execute()` method of `ListIncomeCommand` object which uses the following +methods in `IncomeManager` class to list expenses based on user input category and month. If user does not specify a +specific month, it will show every valid expense instead. + +| Method | Return Type | Relevance | +|-----------------------------|-------------|--------------------------------------------------------------| +| `listIncomes()` | void | Lists out all expenses saved | +| `listIncomesWithMonth(month)` | String | Lists out all expenses saved in specified month | + + +Then, with the variations of `listIncomes()` commands in `IncomeManager`, the list of incomes will be displayed to +the user using the `Ui` class `displayToUser()` method. + +We assume that the command for this feature has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +The following UML Sequence diagram shows how the List Income Feature undergo its execute function. +![ListIncomeSequenceDiagram.drawio.png](diagrams/ListIncomeSequenceDiagram.drawio.png) + +#### 4.2.4 Edit Income Feature +The Edit Income Feature enable users to edit pre-existing entries of incomes in the application. Users are only +allowed to change the date and amount fields of the income field. Currently, the description of each task +cannot be edited, however it may be implemented for future versions. There are 2 sets of instruction that the user +has to input. The first set is to identify which expense to edit, while the second is the fields to be edited. +This feature is controlled by the `EditIncomeCommand` class, where it is initialized by the `Parser` class. The +`Parser` class uses `EditIncomeValidator` to check and extract the `Income` object based on index that the user input. +`EditExpenseCommand` is then created with the extracted `Income` object. + +`EditIncomeCommand` class contains the following attributes, which will be used to store the values that will replace +the existing value in the Income object. During the creation of `EditIncomeCommand`, only `Income` object will be +saved, while the other values will be initialized during subsequent function calls. + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|------------------------------------------------------------------------| +| date | LocalDate | User input date value to replace current date value in expense | +| amount | double | User input amount value to replace current amount value in expense | +| income | Income | User specified Income based on index | + +The `BudgetBuddy` class then calls the `execute()` method of `EditIncomeCommand` object which the `EditIncomeCommand` +class then uses the `Ui` class to call function `getUserEditFields()` for editing the expense parameters. The +`EditIncomeCommand` then uses `EditIncomeValidator` to check and extract for any valid fields. If valid, a +`processEdit()` function will be called to update values in the Income object. + +The UML sequence diagram follows similarly to `Edit Expenses` feature at [4.1.8 Edit Expenses Feature](#418-edit-expense-feature), +where it has one less category field to edit compared to `Edit Expenses`. + +### 4.3 Budget Features + +#### 4.3.1 Add Budget Feature +The Add Budget feature enables users to add budgets for different categories. This functionality is controlled by the +AddBudgetCommand class, which is produced by the Parser class based on user input. The AddBudgetCommand class uses an +AddBudgetValidator object to validate the provided amount, category, and date, and then performs the budget addition +if valid. Below is the relevance of these attributes: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|---------------------------------------------| +| amount | double | The budget amount to be added | +| category | Category | The category of budget to be added | +| date | YearMonth | The specific month and year of budget added | + +The BudgetBuddy class then calls the `execute()` method of the `AddBudgetCommand` object which uses the following +method in the `BudgetManager` class to add a new budget created by the `Budget` class to the `budgets` list +if it does not exist: + +| Method | Return Type | Relevance | +|-------------------|-------------|-----------------------------------------| +| `addBudget(budget)` | void | Add new budget to the list of `budgets` | + +Then, `addAmount(category, amount)` method in the `Budget` class is also called to add the amount to the budget. +Finally, the result is displayed to the user using the `Ui` class `displayToUser()` method. + +The following UML Sequence diagram shows how to obtain the relevant inputs for the Add Budget Feature: + +![AddBudgetSequenceDiagram.drawio.png](diagrams/AddBudgetSequenceDiagram.drawio.png) + +#### 4.3.2 Deduct Budget Feature +The Deduct Budget feature enables users to deduct an amount from an existing budget. This functionality is controlled +by the DeductBudgetCommand class, which is produced by the Parser class based on user input. +The DeductBudgetCommand class uses a DeductBudgetValidator object to validate the provided amount, category, and date, +checks if the specified budget exists, and then performs the deduction if valid. +Below is the relevance of these attributes: + +| Class Attribute | Variable Type | Relevance | +|-----------------|---------------|------------------------------------------------| +| amount | double | The budget amount to be deducted | +| category | Category | The category of budget to be deducted | +| date | YearMonth | The specific month and year of budget deducted | + +The BudgetBuddy class then calls the `execute()` method of the `DeductBudgetCommand` object which uses the +`deductAmount(category, amount)` method in the `Budget` class to deduct amount from the budget. + +The following method in the `BudgetManager` class called by `deductAmount(category, amount)` to delete a budget from +the `budgets` list if the total amount of the budget reaches zero: + +| Method | Return Type | Relevance | +|----------------------|-------------|------------------------------------------| +| `deleteBudget(amount)` | void | Delete budget from the list of `budgets` | + +Finally, the result is displayed to the user using the `Ui` class `displayToUser()` method. + +The following UML Sequence diagram shows how to obtain the relevant inputs for the Deduct Budget Feature: + +![DeductBudgetSequenceDiagram.drawio.png](diagrams/DeductBudgetSequenceDiagram.drawio.png) + +#### 4.3.3 List Budget Feature +The List Budget feature enables users to view all existing budgets or filter them based on the date. This functionality +is controlled by the ListBudgetCommand class, which is produced by the Parser class based on user input. +The ListBudgetCommand class uses a ListBudgetValidator object to validate the provided date, checks if the list request +is valid, and, if valid, retrieves and displays the matching budget through the UI. If the validation fails, +an error message is shown. + +The following UML Sequence diagram shows how to obtain the relevant inputs for the List Budget Feature: + +![ListBudgetSequenceDiagram.drawio.png](diagrams/ListBudgetSequenceDiagram.drawio.png) + +#### 4.3.4 List Remaining Budget Feature +The `ListRemainingBudgetManager` will get the `Expenses` from `ExpenseManager` and `Budgets` from `BudgetManager`. All +the `Expense` amount will be deducted from the budget according to the date and category. + +The following UML Sequence diagram shows the List Remaining Budgets Feature. +We assume that the command for this feature has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at [3.5 Validator Classes](#35-validator-classes). +The loop to copy all the budget and the loop to match a expense to a budget are omitted to reduce complexity. + +![ListRemainingBudgetSequenceDiagram.drawio.png](diagrams/ListRemainingBudgetSequenceDiagram.drawio.png) + +### 4.4 Savings Features + +--- +#### 4.4.1 Display Savings Feature +The Display Savings Feature enables users to check how much they have saved, through their inputs into the application. +We assume that the user has accurately reflected all expenses and incomes, and we calculate their savings by +taking Savings = Total Income - Total Expense. The user has the option to either +display their total savings, or their savings per month since using the app. This feature is managed by the +`DisplaySavingsCommand` class, initialized by the `Parser` class using the `DisplaySavingsValidator` class to display +the correct savings, according to the user input. +The `DisplaySavingsCommand` object is then created with a boolean as an attribute. The class attributes and their +relevance is as follows: + +|Variable Name| Variable Type | Relevance | +|-------------|---------------|-----------------------------------------------------------------------------| +|byMonth| boolean | Indicates whether the user wants to display by month or just total savings. | + +The BudgetBuddy class then calls the `execute()` method of the `DisplaySavingsCommand` object which uses the +`displaySavings()` or `displaySavingsByMonth()` method in the `SavingsManager` class, displaying the result to the +user using the `Ui` class `displayToUser()` method. + +Below is a sequence diagram representing the execution of the Display Expense interaction. +We assume that the `DisplaySavingsCommand` has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![DisplaySavingsSequenceDiagram.drawio.png](diagrams/DisplaySavingsSequenceDiagram.drawio.png) + +Process Overview: +1. The user issues a command to display savings i.e. `display savings m/`. BudgetBuddy parses this input with the help + of the `Parser` class. +2. The `Parser` class calls the `isCommand()` method of the `DisplaySavingsCommand` class, to check if the user input + starts with "display savings". +3. If the user input starts with "display savings", the `Parser` then calls the `processCommand()` method of + `DisplaySavingsValidator` to determine if the user wants a monthly breakdown of savings or simply their total savings. +4. The `processCommand()` method above returns a new `DisplaySavingsCommand` object initialized with a `byMonth` + attribute, set to true if the user wants a monthly breakdown of savings. Otherwise if the user just wants their total + savings, this attribute will be set to false. +5. BudgetBuddy then calls the execute() method of the `DisplaySavingsCommand` object. +6. Depending on the `byMonth` attribute, either `displayTotalSavingsByMonth()` or `displayTotalSavings()` will be + executed from the `SavingsManager` class. +7. The respective methods then save the results in a String, and returns this String to the `DisplaySavingsCommand`, + which then calls the `displayToUser()` method in `Ui`, displaying this String to the user. + +The class diagram below indicates the structure of the DisplaySavings Feature, involving `SavingsManager`, `Saving`, +`IncomeManager` and `ExpenseManager`. + +![SavingsManagerClassDiagram.drawio.png](diagrams/SavingsManagerClassDiagram.drawio.png) + +### 4.5 Income Spent Features + +--- +#### 4.5.1 Display Income Spent Feature +The Display Income Spent Feature shows users the percentage of income spent for a specific month. When the command is +received, BudgetBuddy creates a DisplayIncomeSpentCommand object and executes it. The command then calls +displaySpentPercentage(month) on IncomeSpent, which calculates the percentage for the specified month. The result is +formatted with toString(month: YearMonth) and passed to the Ui component via displayToUser(result: String) for display +to the user. + +The following UML Sequence diagram shows how to obtain the relevant inputs for the Display Income Spent Feature: + +![DisplayIncomeSpentSequenceDiagram.drawio.png](diagrams/DisplayIncomeSpentSequenceDiagram.drawio.png) + + +### 4.6 Miscellaneous Features + +--- +#### 4.6.1 Display Help Feature +The Display Help Feature serves as a guide for new users, displaying a list of features and an example command +for each feature. The feature is managed by the `HelpComand` class, initialized by the `Parser` class upon user input. +The `BudgetBuddy` class then calls the `execute()` method of the `HelpCommand` object, which uses the +`displayHelpMessage()` method of the `Ui` class. + +Below is the sequence diagram for the execution of the Display Help Feature. +We assume that the `HelpCommand` has already been created and returned to `BudgetBuddy`. +The starting arrow indicates return of the command based on the sequence diagram at +[3.5 Validator Classes](#35-validator-classes). + +![DisplayHelpSequenceDiagram.drawio.png](diagrams/DisplayHelpSequenceDiagram.drawio.png) + + +# 5. Appendix + +## 5.1 Product scope +### 5.1.1 Target user profile +For people who have troubles keeping track of their finances and allow them to make more informed financial +decisions. + +### 5.1.2 Value proposition +For students who have difficulties keeping track of expenses and making informed financial choices, BudgetBuddy +acts as a simple-to-use expense and income tracker, as well as a budgeting tool that provides insights into +the user's finances. BudgetBuddy simplifies your financial management, helping you stay organised in a single +application. + +## 5.2 User Stories + +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|-----------------------------------------------------|-------------------------------------------------------------| +| v1.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| v1.0 | user | add and delete income | track my incomes | +| v1.0 | user | add and delete expenses | track my expenses | +| v1.0 | user | view a history of my past income | see how my income changed over time | +| v1.0 | user | view a history of my past expenses | see how my expenses changed over time | +| v1.0 | user | categorise my expenses | understand where my money is going | +| v1.0 | user | search for specific expense entries | quickly find past transactions | +| v1.0 | user | add and delete Monthly Budget | limit my spending | +| v1.0 | user | view a history of my past budgets | see how my spending has changed over time | +| v1.0 | user | save and load my data automatically | view my past entries | +| v2.0 | user | view the trends of my monthly expense | analyse my expense pattern | +| v2.0 | user | view the total expenses for one month | see a summary of my expenses for the month | +| v2.0 | user | view my monthly savings | plan for future expenses | +| v2.0 | user | see where I am spending my money | better manage my expenses | +| v2.0 | user | set a monthly budget for each category | track and manage my spending in different areas | +| v2.0 | user | view the percentage of my total income spent | track my savings | +| v2.0 | user | find a to-do item by name | locate a to-do without having to go through the entire list | +| v2.0 | user | see the budget left | remember to keep to my budget | +| v2.0 | user | get alerts when I am nearing or exceeding my budget | better control my spending | +| v2.0 | user | view the trends of my monthly expenses on a graph | better visualize my spending trends | +| v2.0 | user | view my total expenses for a specific month | better track my monthly expenditure | +| v2.0 | user | edit an existing expense entry | correct any mistakes made to an expense entry | +| v2.0 | user | edit an existing income entry | correct any mistakes made to an income entry | + +## 5.3 Non-Functional Requirements + +1. The Application should work on any *mainstream OS* as long as it has java`17` or above installed. +2. A user with above average typing speed for regular English text should be able to accomplish most of the tasks faster +using commands than using the mouse. + +## 5.4 Glossary + +* *Transactions* - Reference to the different money types in the application [Income, Budget, Expenses] +* *Mainstream OS* - Windows, Linux, Unix, MacOS + +## 5.5 Instructions for manual testing +### 5.5.1. Launching BudgetBuddy +#### 5.5.1.1 Launch + * Download the jar file and copy it to an empty folder. + * Navigate to the jar file via a Terminal window. + * Start the jar file with the command `java -jar BudgetBuddy.jar` + * Expected: Command Line Interface should appear with a welcome message to users, prompting the user for a command. + +### 5.5.2. Test Cases + +#### 5.5.2.1 Displaying command/help list. +1. **Test Case**: `help`
+ Expected: Prints the functionalities of BudgetBuddy, along with the appropriate commands for each feature. +2. **Test Case**: `help 123`
+ Expected: Prints the functionalities of BudgetBuddy, along with the appropriate commands for each feature. + +#### 5.5.2.2 Search Expense +* **2.2.1 No descriptor provided to search for.** + * **Prerequisites**: None + * **Test Case**: `search expense`
+ * **Expected**: Error messsage appears informing the user to key in a valid description to search for. + +* **2.2.2 No expense entry contains given descriptor.** + * **Prerequisites**: At least one expense entry, such that no entry contains the given + descriptor "notInAnyExpense" in its description. + * **Test Case**: `search expense notInAnyExpense`
+ * **Expected**: Message telling user that no expense entry has been found with the given + descriptor ("notInAnyExpense"). + +* **2.2.3 Expense found with given descriptor.** + * **Prerequisites**: At least one expense entry with description containing the given + descriptor "Japan". + * **Test Case**: `search expense Japan` + * **Expected**: Message showing users the expense entries with descriptions that include the word + "Japan". Filtering is case insensitive. + +#### 5.5.2.3 Breakdown Expenses +* **2.3.1 No expense added into the app.** + * **Prerequisites**: No expense tracked in the app. + * **Test Case**: `breakdown expenses` + * **Expected**: Error message prompting user that there are no expense entries. + +* **2.3.2 Expenses added into the app.** + * **Prerequisites**: At least one expense entry tracked by the app. + * **Test Case**: `breakdown expenses` + * **Expected**: Message showing the breakdown of total expenses by category. + +#### 5.5.2.4 Display Savings +* **2.4.1 Display Savings in total** + * **Prerequisites**: At least one expense/income entry tracked by the app. + * **Test Case**: `display savings` + * **Expected**: Message showing the total savings of the user based on the incomes and expenses tracked by + the app, as well as first expense and income entry dates. + +* **2.4.2 Display Savings by month** + * **Prerequisites**: At least one expense/income entry tracked by the app. + * **Test Case**: `display savings m/` + * **Expected**: Message showing the monthly breakdown of savings of the user, based on the incomes and expenses + tracked by the app. + +* **2.4.3 No expense and income entries tracked by the app.** + * **Prerequisites**: App is not tracking any expense or income entries. + * **Test Case**: `display savings m/` or `display savings` + * **Expected**: Message showing that the user has 0 savings. + +### 5.5.2.5 Adding an Expense +* **2.5.1 Adding expense without optional fields (date and category)** + * **Prerequisites**: None + * **Test Case**: `add expense coffee a/5.00` + * **Expected**: Adds an expense with default values and displays success message. + +* **2.5.2 Adding expense with all fields (description, amount, date, category)** + * **Prerequisites**: None + * **Test Case**: `add expense air ticket a/300.00 d/01/11/2024 c/TRANSPORT` + * **Expected**: Success message for added expense. + +* **2.5.3 Adding expense with invalid date format** + * **Prerequisites**: None + * **Test Case**: `add expense movie ticket a/15.00 d/2024-11-01` + * **Expected**: Error message indicating incorrect date format. + +* **2.5.4 Adding expense with negative amount** + * **Prerequisites**: None + * **Test Case**: `add expense lunch a/-5.00` + * **Expected**: Error message indicating amount must be positive. + +### 5.5.2.6 Deleting an Expense +* **2.6.1 Deleting an expense by valid index** + * **Prerequisites**: At least one expense entry in the app. + * **Test Case**: `delete expense 1` + * **Expected**: Deletes the expense and displays confirmation. + +* **2.6.2 Deleting an expense by invalid index** + * **Prerequisites**: At least one expense entry in the app. + * **Test Case**: `delete expense 999` + * **Expected**: Error message indicating index out of range. + +* **2.6.3 Deleting an expense from an empty list** + * **Prerequisites**: No expense entries in the app. + * **Test Case**: `delete expense 1` + * **Expected**: Error message indicating no expenses to delete. + +### 5.5.2.7 Display Monthly Expenses Chart +* **2.7.1 Valid year provided for chart display** + * **Prerequisites**: At least one expense entry in the specified year. + * **Test Case**: `display monthly expenses y/2024` + * **Expected**: XY-Chart displaying total expenses for each month. + +* **2.7.2 Invalid year provided for chart display** + * **Prerequisites**: None + * **Test Case**: `display monthly expenses y/1899` + * **Expected**: Error message for incorrect year. + +* **2.7.3 No expenses tracked in the specified year** + * **Prerequisites**: No expense entries for 2024. + * **Test Case**: `display monthly expenses y/2024` + * **Expected**: Flat XY-Chart showing 0 values for all months. + +### 5.5.2.8 Listing Incomes +* **2.8.1 Listing all incomes** + * **Prerequisites**: At least one income entry tracked. + * **Test Case**: `list incomes` + * **Expected**: Lists incomes and total. + +* **2.8.2 Listing incomes for a specific month** + * **Prerequisites**: At least one income entry in October 2024. + * **Test Case**: `list incomes m/10/2024` + * **Expected**: Lists income entries for October 2024, with total. + +* **2.8.3 No incomes tracked in the app** + * **Prerequisites**: No income entries. + * **Test Case**: `list incomes` + * **Expected**: Message indicating no income entries. + +### 5.5.2.9 Editing Incomes +* **2.9.1 Listing all incomes** + * **Prerequisites**: At least one income entry tracked. + * **Test Case**: `list incomes` + * **Expected**: Lists incomes and total. + +* **2.9.2.A Edit an income from the income list (Selecting an Income)** + * **Prerequisites**: At least one income entry in the overall income list. + * **Test Case**: `edit income 1` + * **Expected**: Displays the income of index 1 to the user and the available field to edit. + +* **2.9.2.B Edit an income from the income list (Editing Income fields)** + * **Prerequisites**: Selected Income is valid from previous step (2.9.2.A). + * **Test Case**: `a/1000 d/10/12/2024` + * **Expected**: Edits the amount and date of the income to *$1000* and *10th December 2024* respectively. + +* **2.9.3 Invalid income index given by user** + * **Prerequisites**: No income entries. + * **Test Case**: `edit income 30` + * **Expected**: Error message indicating invalid income index displayed. + +### 5.5.2.10 Adding a Budget +* **2.10.1 Adding to a monthly budget without specifying month or category** + * **Prerequisites**: None + * **Test Case**: `add budget a/500` + * **Expected**: Adds 500 to current month’s budget. Confirmation message. + +* **2.10.2 Adding to a monthly budget with all fields (amount, date, category)** + * **Prerequisites**: None + * **Test Case**: `add budget a/500 m/09/2024 c/FOOD` + * **Expected**: Adds 500 to September 2024 Food's budget. Confirmation message. + +* **2.10.3 Adding to a monthly budget with invalid date format** + * **Prerequisites**: None + * **Test Case**: `add budget a/1500 m/2024-11` + * **Expected**: Error message indicating incorrect date format. + +* **2.10.4 Adding to a monthly budget with negative amount** + * **Prerequisites**: None + * **Test Case**: `add budget a/-500` + * **Expected**: Error message indicating amount must be positive. -## Design & implementation +### 5.5.2.11 Deducting a Budget +* **2.11.1 Deducting a positive amount from monthly budget** + * **Prerequisites**: Budget more than 200 in current month. + * **Test Case**: `deduct budget a/200` + * **Expected**: Deducts 200 and shows success message. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +* **2.11.2 Deducting an amount that causes budget to go below zero** + * **Prerequisites**: Budget less than 200 in current month. + * **Test Case**: `deduct budget a/200` + * **Expected**: Deducts 200 and shows success message indicating the budget has been deleted. +* **2.11.3 Deducting budget with all fields (amount, date, category)** + * **Prerequisites**: Transport Budget more than 400 in October 2024. + * **Test Case**: `deduct budget a/400 m/10/2024 c/TRANSPORT` + * **Expected**: Deducts 400 from Transport Budget and shows success message. -## Product scope -### Target user profile +### 5.5.2.12 Listing Budgets +* **2.12.1 Listing all budgets** + * **Prerequisites**: At least one budget entry tracked. + * **Test Case**: `list budgets` + * **Expected**: Lists up to 12 latest budgets. -{Describe the target user profile} +* **2.12.2 Listing budget for a specific month** + * **Prerequisites**: At least one budget entry in October 2024. + * **Test Case**: `list budgets m/10/2024` + * **Expected**: Lists budget entry for October 2024. -### Value proposition +* **2.12.3 No budget tracked in the app** + * **Prerequisites**: No budget entry. + * **Test Case**: `list budgets` + * **Expected**: Message indicating no budget entry. -{Describe the value proposition: what problem does it solve?} +* **2.12.4 No budget tracked in the app for specified month** + * **Prerequisites**: No budget entry for specified month. + * **Test Case**: `list budgets m/08/2024` + * **Expected**: Message indicating no budget entry for August 2024. -## User Stories +### 5.5.2.13 Display Income Spent +* **2.13.1 Display percentage of income spent without specifying month** + * **Prerequisites**: At least one expense and one income entries tracked by the app. + * **Test Case**: `display income spent` + * **Expected**: Message showing the percentage of income spent by the user based on the incomes and expenses tracked by + the app for the current month. -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +* **2.13.2 Display percentage of income spent of specified month** + * **Prerequisites**: At least one expense and one income entries tracked by the app in the specified month. + * **Test Case**: `display income spent m/11/2024` + * **Expected**: Message showing the percentage of income spent by the user based on the incomes and expenses tracked by + the app for November 2024. -## Non-Functional Requirements +* **2.13.3 No income entry recorded for specified month** + * **Prerequisites**: No income entry tracked by the app in the specified month. + * **Test Case**: `display income spent m/07/2024` + * **Expected**: Error message showing no income is recorded for July 2024. -{Give non-functional requirements} +### 5.5.2.14 Display Expenses for the Month With Categories Chart +* **2.14.1 Valid Month provided for chart display** + * **Prerequisites**: At least one expenses for one of the categories in the specified month and year. + * **Test Case**: `display expenses with categories m/10/2024` + * **Expected**: PieChart displaying expenses sliced by categories. -## Glossary +* **2.14.2 Invalid Format provided for chart display** + * **Prerequisites**: None + * **Test Case**: `display expenses with categories m/` + * **Expected**: Error message for formatting error. -* *glossary item* - Definition +* **2.14.3 No expenses tracked in the specific month of the year** + * **Prerequisites**: No expense entries for that particular month of the year. + * **Test Case**: `display expenses with categories m/09/2024` + * **Expected**: Empty PieChart shown with values on legends showing all 0. -## Instructions for manual testing +### 5.5.2.15 Exiting BudgetBuddy +* **2.15.1 Exit command** + * **Prerequisites**: None + * **Test Case**: `bye` + * **Expected**: Program exits and saves data automatically, showing exit message. -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} + \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..064562499d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,7 @@ -# Duke +# BudgetBuddy -{Give product intro here} +BudgetBuddy is a desktop application for managing finance and anything related to it, optimised for +use via a Command Line Interface (CLI) Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index d6cf4c3b3a..c7973ca6d2 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,532 @@ # User Guide +--- + +## Table of Contents + +--- + +[Introduction](#introduction)
+[Quick Start](#quick-start)
+[Features](#features-)
+  1. [Expense](#expense)
+    • [Add Expense](#1-adding-an-expense-add-expense)
+    • [Delete Expense](#2-deleting-an-expense-delete-expense)
+    • [List Expenses](#3-list-all-expenses-list-expenses)
+    • [Edit Expenses](#4-edit-pre-existing-expenses-edit-expenses)
+    • [Display Monthly Expenses Chart](#5-display-monthly-expenses-chart-display-monthly-expenses)
+    • [Display Expenses for the Month with Categories Chart](#6-display-expenses-for-the-month-with-categories-chart-display-expenses-with-categories)
+    • [Search Expense](#7-search-expense-search-expenses)
+    • [Breakdown Expenses](#8-breakdown-expenses-breakdown-expenses)
+  2. [Income](#income)
+    • [Add Income](#1-adding-an-income-add-income)
+    • [Delete Income](#2-deleting-an-income-delete-income)
+    • [Edit Income](#3-edit-pre-existing-incomes-edit-incomes)
+    • [List Income](#4-list-all-incomes-list-incomes)
+  3. [Budget](#budget)
+    • [Add Budget](#1-adding-a-budget-add-budget)
+    • [Deduct Budget](#2-deducting-a-budget-deduct-budget)
+    • [List Budget](#3-listing-budgets-list-budgets)
+    • [List Remaining Budget](#4-list-remaining-budgets-list-remaining-budget)
+  4. [Saving](#saving)
+    • [Display Savings](#1-display-savings-display-savings-m)
+  5. [Income Spent](#income-spent)
+    • [Display Income Spent](#1-display-income-spent-display-income-spent)
+  6. [Miscellaneous](#miscellaneous)
+[User Warnings](#user-warnings)
+  1. [Descriptions](#1-descriptions)
+[FAQ](#faq)
+[Command Summary](#command-summary)
+ + ## Introduction +BudgetBuddy is a desktop application for managing finance and anything related to it, optimised for +use via a Command Line Interface (CLI) + +Words in `UPPER_CASE` are the parameters to be supplied by the users. +* e.g in `list incomes [m/MONTH]`, `MONTH` is a parameter which can be used as `list incomes m/10/2024` +* Items in square brackets are optional, e.g: `list incomes` is a valid command. -{Give a product intro} +Extraneous parameters for commands that do not take in parameters (such as `help`) will be ignored. +* e.g. if the command specified is `help 1234`, it will be interpreted as `help` ## Quick Start -{Give steps to get started quickly} +1. Download the JAR File from our GitHub Release and put into your preferred directory. +2. Open your terminal and cd to that directory. +3. Enter `java -jar BudgetBuddy.jar` and enjoy the app! + +## Features + +--- + +### Budget + +--- + +#### 1. Adding a budget: `add budget` +Adds an amount to the month’s current budget. Budget is cumulative for the current month. + +Format: `add budget a/AMOUNT [m/MONTH] [c/CATEGORY]` + +* The `AMOUNT` is the amount of the budget to be added. +* The `MONTH` (Optional) is the month of the budget in MM/YYYY format. If month is not given, current month will be used. +* The `CATEGORY` (Optional) is the category of the budget (FOOD, TRANSPORT, ENTERTAINMENT, EDUCATION, UTILITIES and OTHERS). + The default category is OTHERS. + +Examples of usages: +1. `add budget a/800` +2. `add budget a/700 m/11/2024 c/TRANSPORT` + +Expected Usage of Feature: + +![AddBudgets_ExpectedOutputs.png](images/AddBudgets_ExpectedOutputs.png) + +#### 2. Deducting a budget: `deduct budget` +Deducts an amount to the month’s current budget. The amount deducted will be the smaller of either the entered amount +or the current amount in budget. If budget goes below zero after deduction, the budget for the month will be deleted. + +Format: `deduct budget a/AMOUNT [m/MONTH] [c/CATEGORY]` + +* The `AMOUNT` is the amount of the budget to be deducted. +* The `MONTH` (Optional) is the month of the budget in MM/YYYY format. If month is not given, current month will be used. +* The `CATEGORY` (Optional) is the category of the budget (FOOD, TRANSPORT, ENTERTAINMENT, EDUCATION, UTILITIES and OTHERS). + The default category is OTHERS. + +Examples of usages: +1. `deduct budget a/500` +2. `deduct budget a/400 m/11/2024 c/TRANSPORT` + +Expected Usage of Feature: + +![DeductBudgets_ExpectedOutputs.png](images/DeductBudgets_ExpectedOutputs.png) + +Remarks: Given an example 'Total Monthly Budget: 2000.0 Date: 2024-11 Categories: {FOOD=1000.0, OTHERS=1000}', using +the command "deduct budget a/2000 m/11/2024" will automatically apply the deduction to the "OTHERS" category. +The amount deducted will be the smaller of either 2000 or the current amount in "OTHERS". + +#### 3. Listing budgets: `list budgets` +View all budgets, up to the latest 12 budgets. Able to view the budget for a specific month as well. + +Format: `list budgets [m/MONTH]` + +* The `MONTH` (Optional) is the month of the budget in MM/YYYY format. If month is not given, + up to 12 latest budgets will be listed. + +Examples of usages: +1. `list budgets` +2. `list budgets m/09/2024` + +Expected Usage of Feature: + +![ListBudgets_ExpectedOutput.png](images/ListBudgets_ExpectedOutput.png) + +#### 4. List remaining budgets: `list remaining budget` +Show the budget remaining after deducting the [expenses](#1-adding-an-expense-add-expense) seperated by month and category. + +If the total expenses in any category exceed the budget or if no budget is set, the remaining budget will display as a negative value, indicating an over-budget status. + +Format: `list remaining budget` + +Example of usage: + +`list remaining budget` + +Expected Usage of Feature: + +![ListRemainingBudget_Expected_Output.png](images/ListRemainingBudget_Expected_Output.png) + +### Expense + +--- + +#### 1. Adding an expense: `add expense` +Adds a new expense to the list of expenses. + +Format: `add expense DESCRIPTION a/AMOUNT [d/DATE] [c/CATEGORY]` + +* The `DESCRIPTION` is a short description of the expense. All words without a/c/d tag will be the description +* The `AMOUNT` is the amount of the expense. The amount can have up to 2 decimal points. +* The `DATE` (Optional) is the date of the expense in DD/MM/YYYY format. If date is not given, current date will be used +* The `CATEGORY` (Optional) is the category of the expense (FOOD, TRANSPORT, ENTERTAINMENT, EDUCATION, UTILITIES and OTHERS). If no category or invalid category is given, the default category OTHERS will be used + +Example of usage: + +`add expense air ticket a/123.45` + +`add expense KFC lunch a/10.50 d/28/10/2024 c/FOOD` + +Note: +* Try not to use `|` or `/` in the description. Refer to [descriptions](#1-descriptions) section on how to safely use `|` or `/` in the description. +* If multiple valid `amount`/`date`/`category` are entered, the last valid parameter will be used. +* The remaining budget, after deducting the added expense, will be displayed. +* If the expense causes the budget to be exceeded, a warning message will alert the user. + +Expected Usage of Feature: + +![AddExpense_Expected_Output.png](images/AddExpense_Expected_Output.png) + + +#### 2. Deleting an expense: `delete expense` +Delete an expense from the list of expenses. + +Format: `delete expense INDEX` + +* The `INDEX` is the index of the expense to delete. + +Example of usage: + +`delete expense 1` + +Note: +* The remaining budget, after accounting for the deleted expense, will be displayed. +* If the budget is still less than 0, a warning message will alert the user. + +Expected Usage of Feature: + +![DeleteExpense_Expected_Output.png](images/DeleteExpense_Expected_Output.png) + + + +#### 3. List all expenses: `list expenses` +List the summary of expenses. User could additionally specify which summary type the would like to view. +A final line of total expenses based on summary type will be shown to users. + +Format: +1. `list expenses` +2. `list expenses [m/MONTH]` +3. `list expenses [m/MONTH] [c/CATEGORY]` + +* The `CATEGORY` (Optional) is the summary type to search for expenses with the specified category. +* The `MONTH` (Optional) is the summary type to search for expenses in the specified month in the format of`m/mm/yyyy`. + +Note: +* Only inputs for months are accepted. Any input using date or year are not accepted. +* If any invalid identifiers are given (eg. y/YYYY or d/DD/MM/YYYY), it will ignore these identifiers. + +Example of usage: +`list expenses` +`list expenses m/10/2024` +`list expenses m/10/2024 c/food` + +Expected Usage of Feature: +![ListExpense_ExpectedOutput.png](images/ListExpenses_ExpectedOutput.png) +![ListExpenseWithFilter_ExpectedOutput.png](images/ListExpensesWithFilter_ExpectedOutput.png) + +#### 4. Edit pre-existing expenses: `edit expense` +Edit a pre-existing expense entry details. Users can edit the category, amount and date of the expense field. 2 sets of +input will be required from the user. + +Format: +1. `edit expense INDEX` +2. `[a/AMOUNT c/CATEGORY d/DATE]` + +* The `INDEX` is the index of the desired expenses based on list expense that the user wants to edit. +* The `AMOUNT` (Optional) is the amount to be updated to. +* The `CATEGORY` (Optional) is the category to be updated to. +* The `DATE` (Optional) is the date to be updated to in the format of`d/dd/mm/yyyy`. + + +Note: +* For the second input, at least one of the field must be provided, else it returns back to main menu. + +Example of usage: +1. `edit expense 3` +2. `a/100 c/food d/15/10/2024` + +Expected Usage of Feature: +![EditExpense_ExpectedOutput.png](images/EditExpense_ExpectedOutput.png) + +#### 5. Display Monthly Expenses Chart: `display monthly expenses` +Display a XY-Chart of your monthly expenses. The X-Axis will be the months of the year while the Y-Axis +is the total expenses for that particular month. + +Format: +`display monthly expenses y/YEAR` + +* The `YEAR` is the year of the expenses to be displayed +* The format of `YEAR` should be `YYYY` + +Example of Usage: + +`display monthly expenses y/2024` + +Below is an example of the chart displayed. + +![XY-Chart.png](images/XY-Chart.png) + +#### 6. Display Expenses for the Month with Categories Chart: `display expenses with categories` +Display a PieChart of your expenses for the month, which is sliced by categories. The legend of the PieChart is the +expenses for each category, and the percentage that each slice takes is on the PieChart itself. + +Format: +`display expenses with categories m/MONTH` + +* `MONTH` is the month of the expenses to be displayed +* `MONTH` should be in the form `MM/YYYY` + +Example of Usage: -1. Ensure that you have Java 17 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +`display expenses with categories m/09/2024` -## Features +Below is an example of the chart displayed. -{Give detailed description of each feature} +![PieChart.png](images/PieChart.png) -### Adding a todo: `todo` -Adds a new item to the list of todo items. +#### 7. Search Expense: `search expenses` +Displays the expense entries based on a certain keyword provided by the user. The relevant expense entries are then +filtered based on their description. -Format: `todo n/TODO_NAME d/DEADLINE` +Format: `search expenses KEYWORD(S)` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +* Displays expense entries with descriptions containing the keyword(s) provided by the user. +* If no keyword is provided by the user, an error message will show up, prompting the user to key in a valid keyword. +* If no expenses are found based on the keyword provided, a message indicating that no expense entries are found will + show up. +* Filtering on the provided keyword(s) is case insensitive. -Example of usage: +Example of usage: +1. `search expenses Japan` -`todo n/Write the rest of the User Guide d/next week` +Expected Usage of Feature: +![SearchExpenses_ExpectedOutput.png](images/SearchExpenses_ExpectedOutput.png) -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +#### 8. Breakdown Expenses: `breakdown expenses` +Displays a breakdown of the user’s expenses, by category. The feature displays the total expenditure per category, +along with a percentage indicating the percentage of total expense spent per category. + +Format: `breakdown expenses` + +Example of usage: +1. `breakdown expenses` + +Expected Usage of Feature: +![BreakdownExpenses_ExpectedOutput.png](images/BreakdownExpenses_ExpectedOutput.png) + +### Income + +--- + +#### 1. Adding an income: `add income` +Adds a new income to the list of incomes. + +Format: `add income DESCRIPTION a/AMOUNT [d/DATE]` + +* The `DESCRIPTION` is a short description of the income. All words without a/c/d tag will be the description +* The `AMOUNT` is the amount of the income. The amount can have up to 2 decimal points. +* The `DATE` (Optional) is the date of the expense in DD/MM/YYYY format. If date is not given, current date will be used + +Example of usage: + +`add income tuition a/123.45` + +`add income Tesla stock dividend a/10.50 d/28/10/2024` + +Note: +* Try not to use `|` or `/` in the description. Refer to [descriptions](#1-descriptions) section on how to safely use `|` or `/` in the description. +* If multiple valid `amount`/`date` are entered, the last valid parameter will be used. + +Expected Usage of Feature: + +![AddIncome_Expected_Output.png](images/AddIncome_Expected_Output.png) + + +#### 2. Deleting an income: `delete income` +Delete an income from the list of incomes. + +Format: `delete income INDEX` + +* The `INDEX` is the index of the income to delete. + +Example of usage: + +`delete income 1` + +Expected Usage of Feature: + +![DeleteIncome_Expected_Output.png](images/DeleteIncome_Expected_Output.png) + +#### 3. Edit pre-existing incomes: `edit income` +Edit a pre-existing income entry details. Users can edit the amount and date of the expense field. 2 sets of input +will be required from the user. + +Format: +1. `edit income INDEX` +2. `[a/AMOUNT d/DATE]` + +* The `INDEX` is the index of the desired expenses based on list income that the user wants to edit. +* The `AMOUNT` (Optional) is the amount to be updated to. +* The `DATE` (Optional) is the date to be updated to, in the format of `d/dd/mm/yyyy`. + + +Note: +* For the second input, at least one of the field must be provided, else it returns back to main menu. + +Example of usage: +1. `edit income 3` +2. `a/10000 d/15/10/2024` + +Expected Usage of Feature: +![EditIncomes_ExpectedOutput.png](images/EditIncomes_ExpectedOutput.png) + +#### 4. List all incomes: `list incomes` +List the summary of incomes. User could additionally specify which summary type the would like to view. +A final line of total income based on summary type will be shown to users. + +Format: `list incomes [m/MONTH]` + +* The `MONTH` (Optional) is the summary type to search for incomes in the specified month in the format of`m/mm/yyyy`. + +Note: +- Only inputs for months are accepted. Any inputs using date or year are not accepted. +- If any invalid identifiers are given (eg. y/YYYY or d/DD/MM/YYYY), it will ignore these identifiers. + +Example of usage: +`list incomes` +`list incomes m/10/2024` + +Expected Usage of Feature: +![ListIncomes_ExpectedOutput.png](images/ListIncomes_ExpectedOutput.png) +![ListIncomesWithFilter_ExpectedOutput.png](images/ListIncomesWithFilter_ExpectedOutput.png) + +### Saving + +--- + +#### 1. Display Savings: `display savings [m/]` +Displays the total savings of the user, calculated based on Total Income - Total Expense. User can choose to display +total savings, or a more detailed savings by month based on their input. + +Format: `display savings [m/]` +* If m/ is not provided, total savings of the user is displayed. +* If there are no expense or income entries tracked by the app, total savings of 0 will be displayed. + +Examples of usages: +1. `display savings m/` +2. `display savings` + +Expected Usage of Feature: `display savings m/` +![DisplaySavingsByMonth_ExpectedOutput.png](images/DisplaySavingsByMonth_ExpectedOuput.png) + +Expected Usage of Feature: `display savings` +![DisplaySavings_ExpectedOutput.png](images/DisplaySavings_ExpectedOutput.png) + +### Income Spent + +--- + +#### 1. Display Income Spent: `display income spent` +Displays the percentage of income spent for the specified month. Spending only includes total expenses of the month. + +Format: `display income spent [m/MONTH]` +* The MONTH (Optional) is the month of the budget in MM/YYYY format. If month is not given, current month will be used. + +Examples of usages: +1. `display income spent` +2. `display income spent m/10/2024` + +Expected Usage of Feature:
+![DisplayIncomeSpent_ExpectedOutputs.png](images/DisplayIncomeSpent_ExpectedOutputs.png) + +### Miscellaneous + +--- + +#### 1. Exit Program: `bye` + +Exits the program and save automatically. + +Format: `bye` + +#### 2. Help Sheet: `help` + +Prints the list of commands for the user. + +Format: `help` + +## User Warnings + +--- + +### 1. Descriptions +If the description contains words that start with `a/` or `d/` or `c/`, use capital letters (`A/` or `B/` or `C/`) to prevent invalid error + +For features that requires descriptions, avoid using `|` for description as there can be some unintended results.
+E.g. Taking this command as example: `add expense hi | Bye a/100 d/10/10/2026`.
+This will create a warning as shown below next time the program is started up: ![UserWarning](images/UserWarning.png) + +Users do not need to worry as Storage will automatically ignore and delete the wrongly formatted line, along with +giving a warning to the user, so that they are aware in the future. + +The feature will continue to work as intended if the input is `add expense hi|Bye a/100 d/10/10/2026`. ## FAQ -**Q**: How do I transfer my data to another computer? +--- -**A**: {your answer here} +**Q**: How do I transfer my data to another computer? + +**A**: Copy the BudgetBuddy.txt created under the data folder under the same directory as your JAR file. Transfer the .txt +file to the other computer and replace it with the new .txt file created by your other computer. + +**Q**: What if some of my lines in my file has the wrong formatting? + +**A**: BudgetBuddy automatically skip lines that are corrupted in the data .txt file, so the program will not crash. +To fix the corrupted line, you can either edit the data file yourself to correct the format, or simply delete the line +and execute the command in the JAR file again. + +**Q**: Can I add or create my own category type? + +**A**: As of the current version, creating own category type is currently **not** supported. All features that requires +a category will be grouped as `OTHERS` if no category is provided.o + +**Q**: What would the PieChart show if I have no expenses for the month when I am using `display expenses with categories`? + +**A**: The PieChart will just be empty with the Legend showing that value of all categories to be 0. + +**Q**: What would the XY-Chart show if I have no expenses for some months of the year when I am using `display monthly expenses`? + +**A**: The value at that particular month will just be 0. ## Command Summary -{Give a 'cheat sheet' of commands here} +--- + +### Expense + +- **Add Expense**: `add expense DESCRIPTION a/AMOUNT [d/DATE] [c/CATEGORY]` +- **Delete Expense**: `delete expense INDEX` +- **List Expenses**: `list expenses [c/CATEGORY m/MONTH]` +- **Edit Expense**: + 1. `edit expense INDEX` + 2. `[a/AMOUNT c/CATEGORY d/DATE]` +- **Display Monthly Expenses Chart**: `display monthly expenses y/YEAR` +- **Display Expenses with Categories Chart**: `display expenses with categories m/MM/YYYY` +- **Search Expenses**: `search expenses KEYWORD(S)` +- **Breakdown Expenses**: `breakdown expenses` + +### Income + +- **Add Income**: `add income DESCRIPTION a/AMOUNT [d/DATE]` +- **Delete Income**: `delete income INDEX` +- **Edit Income**: + 1. `edit income INDEX` + 2. `[a/AMOUNT d/DATE]` +- **List Incomes**: `list incomes [m/MONTH]` + +### Budget + +- **Add Budget**: `add budget a/AMOUNT [m/MONTH] [c/CATEGORY]` +- **Deduct Budget**: `deduct budget a/AMOUNT [m/MONTH] [c/CATEGORY]` +- **List Budgets**: `list budgets [m/MONTH]` +- **List Remaining Budget**: `list remaining budget` + +### Saving + +- **Display Savings**: `display savings [m/]` + + +### Income Spent + +- **Display Income Spent**: `display income spent [m/MONTH]` -* Add todo `todo n/TODO_NAME d/DEADLINE` diff --git a/docs/diagrams/AddBudgetSequenceDiagram.drawio.png b/docs/diagrams/AddBudgetSequenceDiagram.drawio.png new file mode 100644 index 0000000000..9b9a050d8e Binary files /dev/null and b/docs/diagrams/AddBudgetSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/AddExpenseSequenceDiagram.drawio.png b/docs/diagrams/AddExpenseSequenceDiagram.drawio.png new file mode 100644 index 0000000000..c565ba0545 Binary files /dev/null and b/docs/diagrams/AddExpenseSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/ApplicationArchitecture.drawio.png b/docs/diagrams/ApplicationArchitecture.drawio.png new file mode 100644 index 0000000000..a4380f4b12 Binary files /dev/null and b/docs/diagrams/ApplicationArchitecture.drawio.png differ diff --git a/docs/diagrams/BreakdownExpenseSequenceDiagram.drawio.png b/docs/diagrams/BreakdownExpenseSequenceDiagram.drawio.png new file mode 100644 index 0000000000..f63d0553b5 Binary files /dev/null and b/docs/diagrams/BreakdownExpenseSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/CommandCreation.drawio.png b/docs/diagrams/CommandCreation.drawio.png new file mode 100644 index 0000000000..43cd365f8f Binary files /dev/null and b/docs/diagrams/CommandCreation.drawio.png differ diff --git a/docs/diagrams/DeductBudgetSequenceDiagram.drawio.png b/docs/diagrams/DeductBudgetSequenceDiagram.drawio.png new file mode 100644 index 0000000000..9e2727c249 Binary files /dev/null and b/docs/diagrams/DeductBudgetSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/DeleteExpenseSequenceDiagram.drawio.png b/docs/diagrams/DeleteExpenseSequenceDiagram.drawio.png new file mode 100644 index 0000000000..3f44b75b05 Binary files /dev/null and b/docs/diagrams/DeleteExpenseSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/DisplayExpensesWithCategories.drawio.png b/docs/diagrams/DisplayExpensesWithCategories.drawio.png new file mode 100644 index 0000000000..2d83832209 Binary files /dev/null and b/docs/diagrams/DisplayExpensesWithCategories.drawio.png differ diff --git a/docs/diagrams/DisplayHelpSequenceDiagram.drawio.png b/docs/diagrams/DisplayHelpSequenceDiagram.drawio.png new file mode 100644 index 0000000000..dd6cd746f5 Binary files /dev/null and b/docs/diagrams/DisplayHelpSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/DisplayIncomeSpentSequenceDiagram.drawio.png b/docs/diagrams/DisplayIncomeSpentSequenceDiagram.drawio.png new file mode 100644 index 0000000000..cf4086c7ea Binary files /dev/null and b/docs/diagrams/DisplayIncomeSpentSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/DisplayMonthlyExpenses.drawio.png b/docs/diagrams/DisplayMonthlyExpenses.drawio.png new file mode 100644 index 0000000000..1e671587af Binary files /dev/null and b/docs/diagrams/DisplayMonthlyExpenses.drawio.png differ diff --git a/docs/diagrams/DisplaySavingsSequenceDiagram.drawio.png b/docs/diagrams/DisplaySavingsSequenceDiagram.drawio.png new file mode 100644 index 0000000000..48c1b21511 Binary files /dev/null and b/docs/diagrams/DisplaySavingsSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/EditExpenseSequenceDiagram.drawio.png b/docs/diagrams/EditExpenseSequenceDiagram.drawio.png new file mode 100644 index 0000000000..661adde8c4 Binary files /dev/null and b/docs/diagrams/EditExpenseSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/EditIncomeSequenceDiagram.drawio.png b/docs/diagrams/EditIncomeSequenceDiagram.drawio.png new file mode 100644 index 0000000000..e26cfaa5aa Binary files /dev/null and b/docs/diagrams/EditIncomeSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/EditSequenceDiagram.drawio.png b/docs/diagrams/EditSequenceDiagram.drawio.png new file mode 100644 index 0000000000..690c7e7920 Binary files /dev/null and b/docs/diagrams/EditSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/EditSequenceDiagram2.drawio.png b/docs/diagrams/EditSequenceDiagram2.drawio.png new file mode 100644 index 0000000000..d5cbaee5f3 Binary files /dev/null and b/docs/diagrams/EditSequenceDiagram2.drawio.png differ diff --git a/docs/diagrams/ExpenseAndIncomeClassDiagram.drawio.png b/docs/diagrams/ExpenseAndIncomeClassDiagram.drawio.png new file mode 100644 index 0000000000..2ce2170ad0 Binary files /dev/null and b/docs/diagrams/ExpenseAndIncomeClassDiagram.drawio.png differ diff --git a/docs/diagrams/ListBudgetSequenceDiagram.drawio.png b/docs/diagrams/ListBudgetSequenceDiagram.drawio.png new file mode 100644 index 0000000000..81a48ccd84 Binary files /dev/null and b/docs/diagrams/ListBudgetSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/ListExpenseSequenceDiagram.drawio.png b/docs/diagrams/ListExpenseSequenceDiagram.drawio.png new file mode 100644 index 0000000000..6c561616c1 Binary files /dev/null and b/docs/diagrams/ListExpenseSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/ListIncomeSequenceDiagram.drawio.png b/docs/diagrams/ListIncomeSequenceDiagram.drawio.png new file mode 100644 index 0000000000..4c6d683b7f Binary files /dev/null and b/docs/diagrams/ListIncomeSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/ListRemainingBudgetSequenceDiagram.drawio.png b/docs/diagrams/ListRemainingBudgetSequenceDiagram.drawio.png new file mode 100644 index 0000000000..4e53aeba9e Binary files /dev/null and b/docs/diagrams/ListRemainingBudgetSequenceDiagram.drawio.png differ diff --git a/docs/diagrams/SavingsManagerClassDiagram.drawio.png b/docs/diagrams/SavingsManagerClassDiagram.drawio.png new file mode 100644 index 0000000000..28dcd06193 Binary files /dev/null and b/docs/diagrams/SavingsManagerClassDiagram.drawio.png differ diff --git a/docs/diagrams/SearchExpenseSequenceDiagram.drawio-3.png b/docs/diagrams/SearchExpenseSequenceDiagram.drawio-3.png new file mode 100644 index 0000000000..4a9c98636b Binary files /dev/null and b/docs/diagrams/SearchExpenseSequenceDiagram.drawio-3.png differ diff --git a/docs/images/AddBudgets_ExpectedOutputs.png b/docs/images/AddBudgets_ExpectedOutputs.png new file mode 100644 index 0000000000..bf28b7113a Binary files /dev/null and b/docs/images/AddBudgets_ExpectedOutputs.png differ diff --git a/docs/images/AddExpense_Expected_Output.png b/docs/images/AddExpense_Expected_Output.png new file mode 100644 index 0000000000..bb243be6c1 Binary files /dev/null and b/docs/images/AddExpense_Expected_Output.png differ diff --git a/docs/images/AddIncome_Expected_Output.png b/docs/images/AddIncome_Expected_Output.png new file mode 100644 index 0000000000..ee8a955fa4 Binary files /dev/null and b/docs/images/AddIncome_Expected_Output.png differ diff --git a/docs/images/BreakdownExpenses_ExpectedOutput.png b/docs/images/BreakdownExpenses_ExpectedOutput.png new file mode 100644 index 0000000000..89da5ec2f7 Binary files /dev/null and b/docs/images/BreakdownExpenses_ExpectedOutput.png differ diff --git a/docs/images/DeductBudgets_ExpectedOutputs.png b/docs/images/DeductBudgets_ExpectedOutputs.png new file mode 100644 index 0000000000..68b5143c49 Binary files /dev/null and b/docs/images/DeductBudgets_ExpectedOutputs.png differ diff --git a/docs/images/DeleteExpense_Expected_Output.png b/docs/images/DeleteExpense_Expected_Output.png new file mode 100644 index 0000000000..3bf025d63c Binary files /dev/null and b/docs/images/DeleteExpense_Expected_Output.png differ diff --git a/docs/images/DeleteIncome_Expected_Output.png b/docs/images/DeleteIncome_Expected_Output.png new file mode 100644 index 0000000000..152a80dc38 Binary files /dev/null and b/docs/images/DeleteIncome_Expected_Output.png differ diff --git a/docs/images/DisplayIncomeSpent_ExpectedOutputs.png b/docs/images/DisplayIncomeSpent_ExpectedOutputs.png new file mode 100644 index 0000000000..b5fa1f5b48 Binary files /dev/null and b/docs/images/DisplayIncomeSpent_ExpectedOutputs.png differ diff --git a/docs/images/DisplaySavingsByMonth_ExpectedOuput.png b/docs/images/DisplaySavingsByMonth_ExpectedOuput.png new file mode 100644 index 0000000000..cab66ea394 Binary files /dev/null and b/docs/images/DisplaySavingsByMonth_ExpectedOuput.png differ diff --git a/docs/images/DisplaySavings_ExpectedOutput.png b/docs/images/DisplaySavings_ExpectedOutput.png new file mode 100644 index 0000000000..4d6fdccfcc Binary files /dev/null and b/docs/images/DisplaySavings_ExpectedOutput.png differ diff --git a/docs/images/EditExpense_ExpectedOutput.png b/docs/images/EditExpense_ExpectedOutput.png new file mode 100644 index 0000000000..ce549170b3 Binary files /dev/null and b/docs/images/EditExpense_ExpectedOutput.png differ diff --git a/docs/images/EditIncomes_ExpectedOutput.png b/docs/images/EditIncomes_ExpectedOutput.png new file mode 100644 index 0000000000..b52c2f5b70 Binary files /dev/null and b/docs/images/EditIncomes_ExpectedOutput.png differ diff --git a/docs/images/ListBudgets_ExpectedOutput.png b/docs/images/ListBudgets_ExpectedOutput.png new file mode 100644 index 0000000000..7075a4c2c4 Binary files /dev/null and b/docs/images/ListBudgets_ExpectedOutput.png differ diff --git a/docs/images/ListExpensesWithFilter_ExpectedOutput.png b/docs/images/ListExpensesWithFilter_ExpectedOutput.png new file mode 100644 index 0000000000..93b1efd8e0 Binary files /dev/null and b/docs/images/ListExpensesWithFilter_ExpectedOutput.png differ diff --git a/docs/images/ListExpenses_ExpectedOutput.png b/docs/images/ListExpenses_ExpectedOutput.png new file mode 100644 index 0000000000..219d370a1c Binary files /dev/null and b/docs/images/ListExpenses_ExpectedOutput.png differ diff --git a/docs/images/ListIncomesWithFilter_ExpectedOutput.png b/docs/images/ListIncomesWithFilter_ExpectedOutput.png new file mode 100644 index 0000000000..87c16af95e Binary files /dev/null and b/docs/images/ListIncomesWithFilter_ExpectedOutput.png differ diff --git a/docs/images/ListIncomes_ExpectedOutput.png b/docs/images/ListIncomes_ExpectedOutput.png new file mode 100644 index 0000000000..dca4df652f Binary files /dev/null and b/docs/images/ListIncomes_ExpectedOutput.png differ diff --git a/docs/images/ListRemainingBudget_Expected_Output.png b/docs/images/ListRemainingBudget_Expected_Output.png new file mode 100644 index 0000000000..1b9139a5d4 Binary files /dev/null and b/docs/images/ListRemainingBudget_Expected_Output.png differ diff --git a/docs/images/PieChart.png b/docs/images/PieChart.png new file mode 100644 index 0000000000..03e4f2b7e0 Binary files /dev/null and b/docs/images/PieChart.png differ diff --git a/docs/images/Portrait_placeholder.png b/docs/images/Portrait_placeholder.png new file mode 100644 index 0000000000..2ae5ef2911 Binary files /dev/null and b/docs/images/Portrait_placeholder.png differ diff --git a/docs/images/SearchExpenses_ExpectedOutput.png b/docs/images/SearchExpenses_ExpectedOutput.png new file mode 100644 index 0000000000..8b5a0e8d1d Binary files /dev/null and b/docs/images/SearchExpenses_ExpectedOutput.png differ diff --git a/docs/images/UserWarning.png b/docs/images/UserWarning.png new file mode 100644 index 0000000000..baffefd47a Binary files /dev/null and b/docs/images/UserWarning.png differ diff --git a/docs/images/XY-Chart.png b/docs/images/XY-Chart.png new file mode 100644 index 0000000000..34e03bf31c Binary files /dev/null and b/docs/images/XY-Chart.png differ diff --git a/docs/team/alfred-goh02.md b/docs/team/alfred-goh02.md new file mode 100644 index 0000000000..68d3cc907a --- /dev/null +++ b/docs/team/alfred-goh02.md @@ -0,0 +1,108 @@ +# Alfred Goh - Project Portfolio Page + +## Overview + +--- + +BudgetBuddy is a desktop application for managing finance and anything related to it, optimized for +use via a Command Line Interface (CLI). It is written in Java and has around 8kLoC. + +## Summary of Contributions + +--- + +### Code Contributed + +Code Contribution to the project: [Reposense Page](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-09-20&tabOpen=true&tabType=authorship&tabAuthor=Alfred-Goh02&tabRepo=AY2425S1-CS2113-W10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Features Implemented +Here is the list of feature implemented: + +#### 1. Storage Feature + +- **What It Does**: User data entered during each session is now automatically saved upon exiting the application and loaded each time the application starts. +- **Justification**: Users benefit from having their data stored automatically, avoiding the need to re-enter information each time they open the application. + +#### 2. Display Monthly Expenses for a Specific Year + +- **What It Does**: Summarizes and displays users' monthly expenses over the course of a year on an XY-chart. The X-axis represents each month, and the Y-axis shows the total expenses for each month. +- **Justification**: The chart provides users with an accessible, visual comparison of their monthly expenses, allowing them to make more informed spending decisions. + +#### 3. Display Expenses for a Month with Categories + +- **What It Does**: Shows a breakdown of users' expenses for a specific month using a pie chart. Each slice of the pie chart represents a category, with the percentage of each slice shown on the chart and its actual value displayed in the legend. +- **Justification**: This feature enables users to easily compare their spending across different categories for a given month, helping them identify areas for improved financial management. + +--- + +### Enhancements Implemented + +#### 1. Unit Tests Across Storage and Expense-Related Classes +- **Input Validation**: Test cases validate inputs and ensure that error messages are returned for invalid entries. +- **Boundary and Edge Case Tests**: Test cases check for negative values, null values, and other edge cases to ensure proper handling. +- **Error Handling**: Test cases confirm that appropriate error messages are displayed when specific errors occur. +- **Data Storage and Loading**: Test cases verify that the save file is created correctly and that data is properly saved and loaded. + +#### 2. Improved Input Validation for Graph Commands +- **Details**: Ensures that different input errors in graph commands yield specific error messages relevant to each type of error. +- **Impact**: This improvement allows users to identify input mistakes immediately and correct them when re-entering commands. + +--- + +### Contributions to User Guide + +- **Documented Sections:** + - `Display Monthly Expenses Chart` + - `Display Expenses for the Month with Categories Chart` + - Command Summary + +**Relevant Pull Request:** [PR #138](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/138/files) + +--- + +### Contributions to Developer Guide + +#### Documented Sections +- **Documentation in Section 3**: + - Section 3.1 `Architecture` + - Section 3.2. `Parser Class` + - Section 3.3. `UI Class` + - Section 3.4 `Command Class` + - Section 3.5 `Validator Classes` + +- **Documentation in Section 4**: + - Section 4.1.5 `Display Monthly Expenses` + - Section 4.1.6 `Display Expenses for the Month with Categories` + +- **Documentation in Section 5**: + - Section 5.5.1.1 `Launch` + - Section 5.5.2.7 `Display Monthly Expenses Chart` + - Section 5.5.2.14 `Display Expenses for the Month with Categories` + - Section 5.5.2.15 `Exit BudgetBuddy` + +#### UML Sequence Diagrams +- Section 3.1: `Architecture` +- Section 3.5: `Validator Classes` +- Section 4.1.5: `Display Monthly Expenses` +- Section 4.1.6: `Display Expenses for the Month with Categories` + +**Relevant Pull Request:** [PR #89](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/89/files) + +--- + +### Contributions to Team-Based Tasks +- **Release Management:** Managed the `v1.0` release. +- **UG and DG Updates:** + - Added Table of Contents to both the User Guide and Developer Guide. + - Added a FAQ section to the User Guide. + - Reformatted the User Guide and Developer Guide with more specific subsections. +- **Useful Libraries:** + - Incorporated XChart into Gradle dependencies, ensuring all dependencies are correctly installed. + - Utilized JFrame to prevent closing XChart windows from ending the application. + - Ensured XChart libraries are correctly built and included in the JAR file. + +--- + +### Contributions Beyond the Project Team +- **Bug Finding and Suggestions:** [Bug Report](https://github.com/Alfred-Goh02/ped/issues) +- **PR Review for Other Teams:** [Pull Request](https://github.com/nus-cs2113-AY2425S1/tp/pull/2/files/b7aac5a22c63db81288d14b15b5d4cb9ecf65418) diff --git a/docs/team/chinorea.md b/docs/team/chinorea.md new file mode 100644 index 0000000000..68f8aededb --- /dev/null +++ b/docs/team/chinorea.md @@ -0,0 +1,58 @@ +# Kenneth Ang - Project Portfolio Page + +## Overview +BudgetBuddy - BudgetBuddy is a desktop application for managing finance and anything related to it, optimised for +use via a Command Line Interface (CLI) + +## Summary of Contributions + +### Code Contribution +List of code contributed : [Reposense Code Link](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=w10-1&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-09-20&tabOpen=true&tabType=authorship&tabAuthor=Chinorea&tabRepo=AY2425S1-CS2113-W10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Feature Implemented +Here is the list of feature implemented: + +| Feature | Purpose | +|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------| +| List Expenses with filters | Allow users to view all past expenses with total value of expenses logged within the application, as well as filter based on category and/or month | +| List Incomes with filters | Allow users to view all past incomes with total value of income logged within the application, as well as filter based on month | +| Edit a Pre-Existing Expense | Allow Users to edit and update an existing expense entry without creating a new entry | +| Edit a Pre-Exisitng Income | Allow Users to edit and update an existing income entry without creating a new entry | + +| Class | Functionality | +|------------------------|--------------------------------------------------------| +| `EditExpenseValidator` | Validate input from user specialised for Edit Expenses | +| `EditIncomeValidator` | Validate input from user specialised for Edit Income | +| `ListIncomeValidator` | Validate input from user specialised for List Income | +| `ListExpenseValidator` | Validate input from user specialised for List Expense | +| `EditExpenseCommand` | Handling of user input values for feature execution | +| `EditIncomeCommand` | Handling of user input values for feature execution | +| `ListIncomeCommand` | Handling of user input values for feature execution | +| `ListExpenseCommand` | Handling of user input values for feature execution | + +### Enhancement Implemented +* Unit Testing of feature implemented above. + * Input Validation: Test cases to validate input and ensure error messages are returned for invalid entries. + * Boundary and Edge Case Tests: Test cases check for negative values, null values and other edge cases to ensure proper handling. + * Error Handling: Test cases confirm that appropriate item or objects are returned when specific error occurs. + +* Two Tier confirmation before editing entries. + * Enable users to confirm selected entry before editing directly. + * Simple exit procedure if chosen entry is incorrect. + +### Contribution to UG +Documented the following portions: +* `ListExpense`, `ListIncome`, `EditIncome` and `EditExpense` feature portions and command summary, +* Introduction portion. + + +### Contribution to DG +Documented the following portions: +* `ListExpense`, `ListIncome`, `EditIncome` and `EditExpense` feature portions in section 4 of the DG +* Prequisites, Introduction, Non-Functional Requirements and Glossary Sections of the DG +* UML Sequence Diagrams for `EditValidatorClass`, `ListExpense`, `EditExpense`, `ListIncome` portions of the DG + + +### Contributions Beyond the Project Team +- **Bug Finding and Suggestions:** [Bug Report](https://github.com/Chinorea/ped/issues) +- **PR Review for Other Teams:** [Pull Request](https://github.com/nus-cs2113-AY2425S1/tp/pull/8/files/b0d61ae8c3b68a0ae2ce7bd0584483847eb39b8d#r1821762776) diff --git a/docs/team/chuacleon.md b/docs/team/chuacleon.md new file mode 100644 index 0000000000..39761e8c08 --- /dev/null +++ b/docs/team/chuacleon.md @@ -0,0 +1,51 @@ +# Chua Wei Qian, Cleon - Project Portfolio Page + +## Overview +BudgetBuddy - BudgetBuddy is a desktop application for managing finance and anything related to it, optimised for +use via a Command Line Interface (CLI). It is written in Java and has about 7 kLOC. + +## Summary of Contributions + +### Code Contribution +List of Code Contributed: [RepoSense Code Link](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=ChuaCleon&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-09-20&tabOpen=true&tabType=authorship&tabAuthor=ChuaCleon&tabRepo=AY2425S1-CS2113-W10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Feature Implemented +Here is the list of features implemented: +1. Display Help Feature +* What it does: Displays the full list of features available in the Budget Buddy application, along with the +appropriate input format for each feature. +* Justification: To provide a guide for new users to the application, improving the user experience for a new user. + +2. Search Expense Feature +* What it does: Filters the expense entries in the app based on a given keyword provided by the user. +* Justification: This feature improves the product because users are now able to easily filter their potentially +long list of expenses and search for specific expense entries. + +3. Breakdown Expenses Feature +* What it does: Provides insights into a user's expenditure by breaking down the user's total expense into the +various categories. The feature then displays the total amount spent per category, as well as the percentage of +total expense spent per category. +* Justification: To allow users to gain greater understanding of how they are spending their money and plan their +finances accordingly. + +4. Display Savings Feature +* What it does: Displays the total savings of the user based on the income and expense entries input by the user. The +user can choose to view total savings or a monthly breakdown of their savings. +* Justification: This feature improves the product because users can keep track of their savings goals, and adjust +their expenses and incomes if necessary. + +### Enhancements to existing features +* Wrote additional tests for existing features to improve coverage from 15% to 35%. ([Pull Request: #222](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/222)) + +### Documentation +* User Guide: + * Added documentation for Display Help Feature, Search Expense Feature, Breakdown Expenses Feature and the + Display Savings Feature. ([#136](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/136)) +* Developer Guide + * Added implementation details and manual testing instructions for Display Help Feature, Search Expense Feature, + Breakdown Expenses Feature and the Display Savings Feature. ([#140](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/140) +, [#148](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/148)) + +### Community +* Reported bugs and suggestions for other groups in the class. ([1](https://github.com/ChuaCleon/ped/issues)) + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/telferang.md b/docs/team/telferang.md new file mode 100644 index 0000000000..acc363dee9 --- /dev/null +++ b/docs/team/telferang.md @@ -0,0 +1,97 @@ +# Ang Zi Le, Telfer - Project Portfolio Page + +## Overview +BudgetBuddy is a desktop application for managing finance and anything related to it, optimised for +use via a Command Line Interface (CLI). It is written in Java, and has around 8kLoC. + +--- + +### Summary of Contributions +I was primarily responsible for implementing the core features related to managing expenses and incomes. This involved creating command classes to process user input, designing the parser to interpret user commands, and ensuring that the user interface (CLI) was intuitive and efficient. + +--- + +### Code Contribution +List of code contributed: [Reposense Code Link](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=telferang&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-09-20) + +--- + +### Project management +- Setting up the GitHub team repo + +--- + +### Feature Implemented +Here is the list of feature implemented: + +#### 1. Add expense and income entries +- **What it does:** Implemented a command-driven approach to allow users to easily add their income and expense records. The program processes the user's input, validates the data, and stores the entries in a structured manner. +- **Justification:** This feature is essential for users to keep a detailed record of their expenses and incomes. + +#### 2. Delete expenses and incomes entries +- **What it does:** Enabled the user to delete entries that were incorrectly added, ensuring the integrity of their records. The command checks for valid indexes and removes the corresponding entry from the data store. +- **Justification:** This feature is necessary if the user added the wrong expense or income + +#### 3. List all the expense and income entries +- **What it does:** Displays a comprehensive list of all expense or income entries recorded by the user, including details like description, amount, date, and category(only for expense). +- **Justification:** This feature gives users a full overview of their spending, making it easier to track patterns, spot areas where they can save, and analyze spending behavior over time. + +#### 4. Show a list of the budget remaining +- **What it does:** Displays the total remaining budget for the current month, after subtracting all the expenses from the corresponding budget entries. +- **Justification:** It enables users to get a quick overview of their financial status, helping them make informed spending decisions for the rest of the month. + +#### 5. Show the budget remaining for the month and category after adding an expense +- **What it does:** After each new expense entry, the program automatically shows the updated budget remaining for the month as well as for the specific expense category. +- **Justification:** Providing this immediate feedback allows users to track spending in real-time, encouraging better budget management and avoiding overspending in specific categories. + +--- + +### Enhancements Implemented + +#### 1. Modify the storage to reject invalid entries +- **Details**: Reject entries with incorrect number of parameter or entries with invalid parameter such as negative amount or invalid date. +- **Impact**: By rejecting invalid entries upfront, I ensured the stability of the application. Users are now prevented from adding records with negative values or incorrect date formats, improving overall data integrity. + +#### 2. Modify the amount to only accept and show 2 decimal place +- **Details**: All amounts are consistently displayed and stored to two decimal places. +- **Impact**: This enhancement addressed issues with floating-point precision errors, ensuring all amounts were consistent and displayed correctly, thus improving the accuracy of financial records. + +--- + +### Contribution to UG +Documented the following portions: +- `Add Expense`, `Delete Expense`, `Add Income`, `Delete Income` and `List Remaining Budget` features, along with command summary. + ([PR #114](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/114)) + +--- + +### Contribution to DG + +- **Documented class design in Section 3:** + - Section 3.6 `Expense and Income Class` + +- **Documented Features in Section 4:** + - Section 4.1.1 `Add Expense` + - Section 4.1.2 `Delete Expense` + - Section 4.2.1 `Add Income` + - Section 4.2.2 `Delete Income` + - Section 4.3.4 `List Remaining Budget` + +- **UML Sequence Diagrams:** + - Created diagrams for the following features: + - Section 3.6 `Expense and Income Class` + - Section 4.1.1 `Add Expense` + - Section 4.1.2 `Delete Expense` + - Section 4.2.1 `Add Income` + - Section 4.2.2 `Delete Income` + - Section 4.3.4 `List Remaining Budget` + +**Relevant Pull Requests**: +([PR #137](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/137)) + +--- + +### Community +- Reported bugs and suggestions for other groups in the class. ([Bug Report](https://github.com/telferang/ped/issues)) +- Provided feedback on pull requests for other groups. ([PR Feedback](https://github.com/nus-cs2113-AY2425S1/tp/pull/25)) + diff --git a/docs/team/ziliaajy.md b/docs/team/ziliaajy.md new file mode 100644 index 0000000000..fe26d00506 --- /dev/null +++ b/docs/team/ziliaajy.md @@ -0,0 +1,107 @@ +# Ang Jia Yu, Zilia - Project Portfolio Page + +## Overview +BudgetBuddy is a desktop application for managing finances and related tasks, optimised for use via a Command Line Interface (CLI). It is developed in Java and contains approximately 8,000 lines of code (LoC). + +--- + +## Summary of Contributions + +### Code Contribution +List of code contributed: [Reposense Code Link](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=ziliaajy&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-09-20&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +--- + +### Feature Implemented +Here is the list of features implemented: + +#### 1. Add Budget Feature +- **What It Does**: Allows users to add a new budget entry by specifying an amount, date, and category. Default date and category used are current month and 'OTHERS'. +- **Justification**: Adding budget entries is fundamental for tracking and managing finances effectively. + +#### 2. Deduct Budget Feature +- **What It Does**: Deducts an amount from an existing budget of a specified category, updating the remaining balance for the month. +- **Justification**: Essential for adjusting budgets accurately. + +#### 3. Listing Budget Feature +- **What It Does**: Displays a list of up to 12 latest budgets, categorised by month, showing specific categories, amounts, and dates. +Users can also request to view a specific month’s budget. +- **Justification**: Provides users a summary of budget allocations, with a manageable view by limiting to the latest 12 months. + +#### 4. Display Income Spent Feature +- **What It Does**: Calculates and displays the percentage of income spent for a specific month, based on recorded expenses and income. +- **Justification**: Offers a quick overview of spending habits, helping users assess if they are staying within their income. + +--- + +### Enhancements Implemented + +#### 1. Improved Input Validation for Budget Commands +- **Details**: Enhanced validation for budget-related commands, ensuring unexpected text or invalid formats trigger clear error messages. +- **Impact**: Reduces user error and improves guidance, making the application more intuitive. + +#### 2. Refined UI Feedback for Budget Listings +- **Details**: Adjusted the budget listing feature to display each category's budget more clearly. +- **Impact**: Helps users quickly identify budgets by category. + +#### 3. Common Tests Across Budget-related and Display Income Spent Features +- **Input Validation**: Ensures invalid inputs (e.g., incorrect formats) trigger errors. +- **Boundary and Edge Case Tests**: Verifies handling of zero, negative values, and edge cases. +- **Error Handling**: Confirms appropriate error messages for invalid inputs. +- **Data Retrieval**: Tests retrieval of specific data (e.g., budgets for a specific month). + + +- **Relevant Pull Requests**: + - [PR #29](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/29) + - [PR #131](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/131) + - [PR #151](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/151) + +--- + +### Contribution to UG +Documented the following portions: +- `Add Budget` +- `Deduct Budget` +- `List Budgets` +- `Display Income Spent` +- Command Summary + +**Relevant Pull Request**: [PR #141](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/141) + +--- + +### Contribution to DG + +- **Documented Features in Section 4:** + - Section 4.3.1 `Add Budget` + - Section 4.3.2 `Deduct Budget` + - Section 4.3.3 `List Budget` + - Section 4.5.1 `Display Income Spent` + +- **Manual Testing Instructions in Section 5:** + - Instructions for the following features: + - Section 5.5.2.10 `Add Budget` + - Section 5.5.2.11 `Deduct Budget` + - Section 5.5.2.12 `List Budget` + - Section 5.5.2.13 `Display Income Spent` + +- **UML Sequence Diagrams:** + - Created diagrams for the following features: + - Section 4.3.1 `Add Budget` + - Section 4.3.2 `Deduct Budget` + - Section 4.3.3 `List Budget` + - Section 4.5.1 `Display Income Spent` + +**Relevant Pull Request**: [PR #135](https://github.com/AY2425S1-CS2113-W10-1/tp/pull/135) + +--- + +### Contributions to Team-Based Tasks +- **Project Management:** Contributed to planning discussions, helped filter and shape the project's features and structure. +- **Release Management:** Managed the `v2.0` release. + +--- + +### Community +- Reported bugs and suggestions for other groups in the class. ([Bug Report](https://github.com/ZiliaAJY/ped/issues)) +- Provided feedback on pull requests for other groups. ([PR Feedback](https://github.com/nus-cs2113-AY2425S1/tp/pull/25)) diff --git a/gradlew b/gradlew index fcb6fca147..187404fa8b 100755 --- a/gradlew +++ b/gradlew @@ -25,7 +25,7 @@ # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole -# command line, like: +# commands line, like: # # ksh Gradle # @@ -35,7 +35,7 @@ # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * various built-in commands including «commands», «set», and «ulimit». # # Important for patching: # @@ -114,7 +114,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar -# Determine the Java command to use to start the JVM. +# Determine the Java commands to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables @@ -158,8 +158,8 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then esac fi -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line +# Collect all arguments for the java commands, stacking in reverse order: +# * args from the commands line # * the main class name # * -classpath # * -D...appname settings @@ -201,7 +201,7 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; +# Collect all arguments for the java commands; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and @@ -228,7 +228,7 @@ fi # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # -# but POSIX shell has neither arrays nor command substitution, so instead we +# but POSIX shell has neither arrays nor commands substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap diff --git a/lib/xchart-3.8.8.jar b/lib/xchart-3.8.8.jar new file mode 100644 index 0000000000..efbef897ba Binary files /dev/null and b/lib/xchart-3.8.8.jar differ diff --git a/src/main/java/seedu/budgetbuddy/BudgetBuddy.java b/src/main/java/seedu/budgetbuddy/BudgetBuddy.java new file mode 100644 index 0000000000..e636dd6bfa --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/BudgetBuddy.java @@ -0,0 +1,88 @@ +package seedu.budgetbuddy; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.ExitCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.io.IOException; + +/** + * Runs the main class for the BudgetBuddy application. + * + * This class initiates the BudgetBuddy application by managing the overall lifecycle + * of the application, including loading saved data, handling user commands, and saving + * changes to a file. It processes user commands until the exit command is invoked. + */ +public class BudgetBuddy { + private Storage storage; + private ExpenseManager expenseManager; + private IncomeManager incomeManager; + private BudgetManager budgetManager; + + /** + * Constructs a new BudgetBuddy instance, initializing file storage and loading saved data. + * It attempts to create a file if it does not exist and loads data from the provided file path. + * + * @param filepath The path to the file where expenses, incomes, and budgets are stored. + */ + public BudgetBuddy(String filepath) { + storage = new Storage(filepath); + + expenseManager = new ExpenseManager(); + incomeManager = new IncomeManager(); + budgetManager = new BudgetManager(); + try { + storage.createFileIfNotExists(); + storage.load(); + + } catch (IOException e) { + Ui.showMessage("Error updating File"); + } + } + + /** + * Runs the BudgetBuddy application. Displays the welcome message and + * continuously processes user commands until the exit command is executed. + * After each command, it saves any changes to the storage file. + */ + public void run() { + Ui.displayWelcomeMessage(); + Command command = null; + Parser parser = new Parser(expenseManager, incomeManager, budgetManager); + do { + String userCommandText = Ui.getUserCommand(); + try { + command = parser.parseCommand(userCommandText); + command.execute(); + } catch (BudgetBuddyException e) { + System.out.println(e.getMessage()); + } catch (Exception e) { + // Prevent the code from crashing + System.out.println("An error has occurred"); + } + try { + storage.save(expenseManager, incomeManager, budgetManager); + } catch (IOException e) { + Ui.showMessage("Error updating File"); + } catch (Exception e) { + // Prevent the code from crashing + System.out.println("An error has occurred"); + } + } while (!(command instanceof ExitCommand)); + System.exit(0); + } + + /** + * The entry point for the BudgetBuddy application. Creates a new BudgetBuddy instance + * with the specified file path and starts the application. + * + * @param args Command-line arguments (not used). + */ + public static void main(String[] args) { + new BudgetBuddy("./data/BudgetBuddy.txt").run(); + } +} + diff --git a/src/main/java/seedu/budgetbuddy/Parser.java b/src/main/java/seedu/budgetbuddy/Parser.java new file mode 100644 index 0000000000..f8ed5afaa6 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/Parser.java @@ -0,0 +1,252 @@ +package seedu.budgetbuddy; + +import seedu.budgetbuddy.commands.budget.ListRemainingBudgetCommand; +import seedu.budgetbuddy.commands.expense.BreakdownExpensesCommand; +import seedu.budgetbuddy.commands.expense.DeleteExpenseCommand; +import seedu.budgetbuddy.commands.expense.AddExpenseCommand; +import seedu.budgetbuddy.commands.expense.DisplayExpensesForMonthWithCategoriesGraphCommand; +import seedu.budgetbuddy.commands.expense.EditExpenseCommand; +import seedu.budgetbuddy.commands.expense.SearchExpenseCommand; +import seedu.budgetbuddy.commands.expense.ListExpenseCommand; +import seedu.budgetbuddy.commands.expense.DisplayTotalExpensesCommand; +import seedu.budgetbuddy.commands.income.AddIncomeCommand; +import seedu.budgetbuddy.commands.budget.AddBudgetCommand; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.budget.DeductBudgetCommand; +import seedu.budgetbuddy.commands.income.DeleteIncomeCommand; +import seedu.budgetbuddy.commands.ExitCommand; +import seedu.budgetbuddy.commands.HelpCommand; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.budget.ListBudgetCommand; +import seedu.budgetbuddy.commands.income.DisplayIncomeSpentCommand; +import seedu.budgetbuddy.commands.income.EditIncomeCommand; +import seedu.budgetbuddy.commands.income.ListIncomeCommand; +import seedu.budgetbuddy.commands.saving.DisplaySavingsCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.validators.expense.DisplayExpensesForMonthWithCategoriesValidator; +import seedu.budgetbuddy.validators.expense.EditExpenseValidator; +import seedu.budgetbuddy.validators.expense.ListExpenseValidator; +import seedu.budgetbuddy.validators.income.AddIncomeValidator; +import seedu.budgetbuddy.validators.budget.AddBudgetValidator; +import seedu.budgetbuddy.validators.budget.DeductBudgetValidator; +import seedu.budgetbuddy.validators.income.DeleteIncomeValidator; +import seedu.budgetbuddy.validators.income.DisplayIncomeSpentValidator; +import seedu.budgetbuddy.validators.income.EditIncomeValidator; +import seedu.budgetbuddy.validators.income.ListIncomeValidator; +import seedu.budgetbuddy.validators.budget.ListBudgetValidator; +import seedu.budgetbuddy.validators.expense.AddExpenseValidator; +import seedu.budgetbuddy.validators.expense.DeleteExpenseValidator; +import seedu.budgetbuddy.validators.expense.DisplayTotalExpensesValidator; +import seedu.budgetbuddy.validators.expense.SearchExpenseValidator; +import seedu.budgetbuddy.validators.saving.DisplaySavingsValidator; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +/** + * The Parser class is responsible for interpreting user commands. + * It analyzes the user input, identifies the corresponding command, + * and returns the appropriate Command object for execution. + */ +public class Parser { + private static ExpenseManager expenseManager; + private static IncomeManager incomeManager; + private static BudgetManager budgetManager; + + public Parser(ExpenseManager expenseManager, IncomeManager incomeManager, BudgetManager budgetManager) { + Parser.expenseManager = expenseManager; + Parser.incomeManager = incomeManager; + Parser.budgetManager = budgetManager; + } + + /** + * Analyzes the user's input and returns the appropriate {@code Command} object. + * It checks the input against known commands for expense, income, or exit operations. + * + * @param userCommandText The input string provided by the user. + * @return The corresponding {@code Command} to execute, or an {@code IncorrectCommand} + * if the input is invalid. + */ + public Command parseCommand(String userCommandText) throws BudgetBuddyException { + if (AddExpenseCommand.isCommand(userCommandText)) { + return AddExpenseValidator.processCommand(userCommandText); + } + if (DeleteExpenseCommand.isCommand(userCommandText)) { + return DeleteExpenseValidator.processCommand(userCommandText); + } + if (ListExpenseCommand.isCommand(userCommandText)) { + return ListExpenseValidator.processCommand(userCommandText); + } + if (AddIncomeCommand.isCommand(userCommandText)) { + return AddIncomeValidator.processCommand(userCommandText); + } + if (DeleteIncomeCommand.isCommand(userCommandText)) { + return DeleteIncomeValidator.processCommand(userCommandText); + } + if (AddBudgetCommand.isCommand(userCommandText)) { + return AddBudgetValidator.processCommand(userCommandText); + } + if (DeductBudgetCommand.isCommand(userCommandText)) { + return DeductBudgetValidator.processCommand(userCommandText); + } + if (ListBudgetCommand.isCommand(userCommandText)) { + return ListBudgetValidator.processCommand(userCommandText); + } + if (ExitCommand.isCommand(userCommandText)) { + return new ExitCommand(); + } + if (HelpCommand.isCommand(userCommandText)){ + return new HelpCommand(); + } + if (ListIncomeCommand.isCommand(userCommandText)) { + return ListIncomeValidator.processCommand(userCommandText); + } + if (SearchExpenseCommand.isCommand(userCommandText)){ + return SearchExpenseValidator.processCommand(userCommandText); + } + if (DisplayTotalExpensesCommand.isCommand(userCommandText)){ + return DisplayTotalExpensesValidator.processCommand(userCommandText); + } + if (DisplayIncomeSpentCommand.isCommand(userCommandText)) { + return DisplayIncomeSpentValidator.processCommand(userCommandText); + } + if(EditExpenseCommand.isCommand(userCommandText)){ + return EditExpenseValidator.processFirstCommand(userCommandText); + } + if(EditIncomeCommand.isCommand(userCommandText)){ + return EditIncomeValidator.processFirstCommand(userCommandText); + } + if (ListRemainingBudgetCommand.isCommand(userCommandText)) { + return new ListRemainingBudgetCommand(); + } + if (DisplaySavingsCommand.isCommand(userCommandText)){ + return DisplaySavingsValidator.processCommand(userCommandText); + } + if (BreakdownExpensesCommand.isCommand(userCommandText)){ + return new BreakdownExpensesCommand(); + } + if (DisplayExpensesForMonthWithCategoriesGraphCommand.isCommand(userCommandText)){ + return DisplayExpensesForMonthWithCategoriesValidator.processCommand(userCommandText); + } + return new IncorrectCommand("Invalid input"); + } + + /** + * Parses a line of input from the file and categorizes it as an expense, income, or budget. + * Each line is split based on the delimiter " | ", and the resulting parts are used to create + * the appropriate object (Expense, Income, or Budget). + * + * @param input The line of text from the file to be parsed. + */ + public static void parseFile(String input) { + + String[] parts = input.split(" \\| "); + String type = parts[0]; // Determines if it's expense, income, or budget + + switch (type.toLowerCase()) { + case "expense": + parseExpense(input, parts); + break; + + case "income": + parseIncome(input, parts); + break; + + case "budget": + parseBudget(input, parts); + break; + + default: + System.out.println("Unknown type in file: " + type); + break; + } + } + + private static void parseBudget(String input, String[] parts) { + try { + if (parts.length != 4) { + Ui.showMessage("Invalid Storage Format: " + input); + return; + } + YearMonth budgetDate = YearMonth.parse(parts[2], DateTimeFormatter.ofPattern("yyyy-MM")); + + if (BudgetManager.getBudget(budgetDate) != null) { + Ui.showMessage("Repeated budget entry: " + input); + return; + } + + // Adjust date format for YearMonth + String categoryPart = parts[3].trim(); + categoryPart = categoryPart.substring(1, categoryPart.length() - 1); + Budget budget = new Budget(budgetDate); + + String[] categories = categoryPart.split(", "); + if (addBudgetCategoryAmount(input, categories, budget)) { + return; + } + BudgetManager.addBudget(budget); + } catch (Exception e) { + Ui.showMessage("Invalid Input Format: " + input); + } + } + + private static boolean addBudgetCategoryAmount(String input, String[] categories, Budget budget) { + for (String categoryEntry : categories) { + String[] categorySplit = categoryEntry.split("="); + Category category = Category.valueOf(categorySplit[0].toUpperCase()); + double categoryAmount = Double.parseDouble(categorySplit[1]); + if (categoryAmount < 0) { + Ui.showMessage("Invalid Storage Format: " + input); + return true; + } + budget.addAmount(category, categoryAmount); + } + return false; + } + + private static void parseIncome(String input, String[] parts) { + try { + if (parts.length != 4) { + Ui.showMessage("Invalid Storage Format: " + input); + return; + } + String description = parts[1]; + double amount = Double.parseDouble(parts[2]); + LocalDate date = LocalDate.parse(parts[3], DateTimeFormatter.ofPattern("d/M/yyyy")); + if (amount < 0) { + Ui.showMessage("Invalid Storage Format: " + input); + return; + } + IncomeManager.loadIncome(new Income(description, amount, date)); // No category needed for income + } catch (Exception e) { + Ui.showMessage("Invalid Input Format: " + input); + } + } + + private static void parseExpense(String input, String[] parts) { + try { + if (parts.length != 5) { + Ui.showMessage("Invalid Storage Format: " + input); + return; + } + String description = parts[1]; + double amount = Double.parseDouble(parts[2]); + LocalDate date = LocalDate.parse(parts[3], DateTimeFormatter.ofPattern("d/M/yyyy")); + Category category = Category.valueOf(parts[4].toUpperCase()); // Ensure category exists for expense + if (amount < 0) { + Ui.showMessage("Invalid Storage Format: " + input); + return; + } + ExpenseManager.loadExpense(new Expense(description, amount, date, category)); + } catch (Exception e) { + Ui.showMessage("Invalid Storage Format: " + input); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/Storage.java b/src/main/java/seedu/budgetbuddy/Storage.java new file mode 100644 index 0000000000..925ab7772c --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/Storage.java @@ -0,0 +1,177 @@ +package seedu.budgetbuddy; + +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.Transaction; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Map; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The Storage class is responsible for handling the reading and writing of data from + * and to the disk. It manages the creation of files, loading data into the program, + * and saving the state of the Expense, Income, and Budget transactions. + * @author Alfred-Goh02 + */ +public class Storage { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private String filePath; + + /** + * Initializes the Storage object with the specified file path. + * + * @param filepath The path to the file where data will be saved and loaded. + */ + public Storage(String filepath) { + this.filePath = filepath; + LOGGER.log(Level.INFO, "Storing " + filepath); + } + + /** + * Loads the data from the file located at the specified file path. + * It parses the file contents and converts it into the respective Expense, Income, + * and Budget objects, storing them in lists. + * + * @@author Alfred-Goh02 + * @throws FileNotFoundException If the file at the specified path does not exist. + */ + public void load() throws FileNotFoundException { + File file = new File(filePath); + if (!file.exists()) { + LOGGER.warning("File does not exist: " + file.getAbsolutePath()); + throw new FileNotFoundException("File does not exist: " + file.getAbsolutePath()); + } + ArrayList expenses = new ArrayList<>(); + ArrayList incomes = new ArrayList<>(); + ArrayList budgets = new ArrayList<>(); + Scanner sc = new Scanner(file); + + while (sc.hasNextLine()) { + String input = sc.nextLine(); + LOGGER.fine("Parsing line: " + input); // Log each line being parsed + Parser.parseFile(input); + } + sc.close(); + } + + /** + * Saves the current state of the Expense, Income, and Budget data to the file. + * The file is overwritten with the latest data. + * + * @@author Alfred-Goh02 + * @param expenseList The ExpenseManager containing the current list of expenses. + * @param incomeList The IncomeManager containing the current list of incomes. + * @param budgetList The BudgetManager containing the current list of budgets. + * @throws IOException If an I/O error occurs while writing to the file. + */ + public void save(ExpenseManager expenseList, IncomeManager incomeList, BudgetManager budgetList) + throws IOException { + + assert expenseList != null : "Expense list cannot be null"; + assert incomeList != null : "Income list cannot be null"; + assert budgetList != null : "Budget list cannot be null"; + LOGGER.info("Saving data to file: " + filePath); + + FileWriter fw = new FileWriter(filePath, false); + + // Save expenses + for (Expense expense : ExpenseManager.getExpenses()) { + if (expense != null) { + String line = getString(expense); + fw.write(line + System.lineSeparator()); + } + } + + // Save incomes + for (Income income : IncomeManager.getIncomes()) { + if (income != null) { + String line = getString(income); + fw.write(line + System.lineSeparator()); + } + } + + // Save budgets + for (Budget budget : BudgetManager.getBudgets()) { + if (budget != null) { + String line = getString(budget.getTotalMonthlyBudget(), budget.getDate(), budget.getCategoryBudgets()); + fw.write(line + System.lineSeparator()); + } + } + fw.close(); + } + + /** + * Converts a Transaction object into a string representation for saving to the file. + * The string format differs depending on whether the transaction is an Expense or an Income. + * + * @param transaction The transaction to be converted. + * @return A string representation of the transaction. + */ + private String getString(Transaction transaction) { + StringBuilder line = new StringBuilder(); + + if (transaction instanceof Expense expense) { + line.append("expense | ") + .append(expense.getDescription()).append(" | ") + .append(expense.getAmount()).append(" | ") + .append(expense.getDate().format(DateTimeFormatter.ofPattern("d/M/yyyy"))).append(" | ") + .append(expense.getCategory()); + } else if (transaction instanceof Income income) { + line.append("income | ") + .append(income.getDescription()).append(" | ") + .append(income.getAmount()).append(" | ") + .append(income.getDate().format(DateTimeFormatter.ofPattern("d/M/yyyy"))); + } + return line.toString(); + } + + /** + * Converts the total monthly budget, date, and category-specific budgets into a formatted string + * representation for saving to a file. + * + * @param totalBudget The total budget for the month. + * @param date The date in the format YYYY-MM representing the budget month. + * @param categoryBudgets A map containing category names as keys and their respective budget amounts as values. + * @return A formatted string representing the total budget, date, and category-specific budgets. + */ + private String getString(double totalBudget, YearMonth date, Map categoryBudgets) { + StringBuilder line = new StringBuilder(); + line.append("budget | "); + line.append(totalBudget).append(" | "); + line.append(date.format(DateTimeFormatter.ofPattern("yyyy-MM"))).append(" | "); + line.append(categoryBudgets.toString()); + return line.toString(); + } + + /** + * Creates a new file at the specified path if it does not already exist. + * If the file's parent directories do not exist, they will be created. + * + * @throws IOException If an error occurs while creating the file. + */ + public void createFileIfNotExists() throws IOException { + File file = new File(filePath); + if (!file.exists()) { + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + file.createNewFile(); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/Ui.java b/src/main/java/seedu/budgetbuddy/Ui.java new file mode 100644 index 0000000000..f83f916966 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/Ui.java @@ -0,0 +1,189 @@ +package seedu.budgetbuddy; + +import java.time.YearMonth; +import java.util.Scanner; + +/** + * The Ui class handles all user interactions by displaying messages to the user and + * receiving input from them. + */ +public class Ui { + + public static final String SEPARATOR = "========================================================"; + public static final String WELCOME_MESSAGE = "Welcome to Budget Buddy!"; + public static final String EXIT_MESSAGE = "Bye!"; + private static final Scanner scanner = new Scanner(System.in); + + /** + * Retrieves the user's command input. This method ensures that the user + * does not submit an empty command by repeatedly prompting until valid input is provided. + * + * @return The user’s command as a trimmed {@code String}. + */ + public static String getUserCommand() { + String inputCommand; + + do { + System.out.print("Enter commands: "); + inputCommand = scanner.nextLine().trim(); + + if (inputCommand.isEmpty()) { + displayToUser("Input cannot be empty. Please try again."); + } + } while (inputCommand.isEmpty()); + return inputCommand; + } + + /** + * Retrieves the user's command input. + * If an empty command is received, it will end the command and give an + * acknowledgment message. + * + * @return The user’s command as a trimmed {@code String}. + */ + public static String getUserEditFields(){ + String inputCommand; + + do { + System.out.print("Enter edit Field: "); + inputCommand = scanner.nextLine().trim(); + + if (inputCommand.isEmpty()) { + displayToUser("Empty Input Detected, Exiting change menu."); + return ""; + } + } while (inputCommand.isEmpty()); + return inputCommand; + } + + /** + * Displays a given message to the user, surrounded by separators for readability. + * + * @param message The message to display to the user. + */ + public static void displayToUser(String message) { + System.out.println(SEPARATOR); + System.out.println(message); + System.out.println(SEPARATOR); + } + + /** + * Displays the welcome message when the application starts. + */ + public static void displayWelcomeMessage() { + displayToUser(WELCOME_MESSAGE); + } + + /** + * Displays the exit message when the application ends. + */ + public static void displayExitMessage() { + displayToUser(EXIT_MESSAGE); + } + + /** + * Displays a message regarding a budget transaction, indicating updating the amount, + * and shows the current total number of budgets. + * + * @param transaction The details of the budget transaction. + * @param count The total number of budgets after the transaction. + */ + public static void displayBudgetTransactionMessage(String transaction, int count) { + String result = "The following budget amount has been updated:\n" + + transaction + '\n' + + "You have " + count + " budget(s) in total."; + displayToUser(result); + } + + /** + * Displays a message indicating that a budget has been deleted because its amount has reached zero, + * and shows the current total number of remaining budgets. + * + * @param month The month of the deleted budget. + * @param count The total number of budgets after the deletion. + */ + public static void displayBudgetDeletedMessage(YearMonth month, int count) { + String result = "The following budget has been deleted as its amount reached zero:\n" + + "Date: " + month + '\n' + + "You have " + count + " budget(s) in total."; + displayToUser(result); + } + + /** + * Displays entire help message, acts as a help guide for new users. + */ + public static void displayHelpMessage() { + // ANSI color codes for text formatting + String reset = "\u001B[0m"; + String bold = "\u001B[1m"; + String green = "\u001B[32m"; + String yellow = "\u001B[33m"; + String cyan = "\u001B[36m"; + + String message = + bold + cyan + "=== BudgetBuddy Help Guide ===" + reset + "\n\n" + + bold + yellow + "Budget Commands:" + reset + "\n" + + green + "- add budget a/AMOUNT [m/MONTH] [c/CATEGORY]:" + reset + " Add a budget for the " + + "current month or a specified month/category." + "\n" + + green + "- deduct budget a/AMOUNT [m/MONTH] [c/CATEGORY]:" + reset + " Deduct from a budget" + + " for the current month or a specified month/category." + "\n" + + green + "- list budgets [m/MONTH]:" + reset + " List all budgets, or budgets for a specific" + + " month." + "\n" + + green + "- list remaining budget:" + reset + " List remaining budget after expenses for each" + + " month and category." + "\n\n" + + + bold + yellow + "Expense Commands:" + reset + "\n" + + green + "- add expense DESCRIPTION a/AMOUNT [d/DATE] [c/CATEGORY]:" + reset + " Add an" + + " expense with a description, amount, date, and category." + "\n" + + green + "- delete expense INDEX:" + reset + " Delete an expense by its index." + "\n" + + green + "- list expenses [m/MONTH] [c/CATEGORY]:" + reset + " List all expenses, or filter" + + " by month or category." + "\n" + + green + "- edit expense INDEX [a/AMOUNT] [c/CATEGORY] [d/DATE]:" + reset + " Edit an " + + "existing expense by index and modify its amount, category, or date." + "\n" + + green + "- display monthly expenses y/YEAR:" + reset + " Display a chart of monthly " + + "expenses for a given year." + "\n" + + green + "- display expenses with categories m/MONTH:" + reset + " Display a pie chart of " + + "expenses by category for a given month." + "\n" + + green + "- search expenses KEYWORD(S):" + reset + " Search expenses by keyword(s)." + "\n" + + green + "- breakdown expenses:" + reset + " Display a breakdown of expenses by " + + "category." + "\n\n" + + + bold + yellow + "Income Commands:" + reset + "\n" + + green + "- add income DESCRIPTION a/AMOUNT [d/DATE]:" + reset + " Add an income with a " + + "description, amount, and date." + "\n" + + green + "- delete income INDEX:" + reset + " Delete an income by its index." + "\n" + + green + "- edit income INDEX [a/AMOUNT] [d/DATE]:" + reset + " Edit an existing income by " + + "index and modify its amount or date." + "\n" + + green + "- list incomes [m/MONTH]:" + reset + " List all incomes or filter by month." + "\n" + + green + "- display income spent [m/MONTH]:" + reset + " Display income spent as a " + + "percentage of total income for the month." + "\n\n" + + + bold + yellow + "Savings Commands:" + reset + "\n" + + green + "- display savings [m/]:" + reset + " Display the savings after deducting " + + "expenses from incomes." + "\n\n" + + + // Explanation for optional and user input formatting + reset + bold + cyan + "[ ] is optional. Commands with [ ] allow you to choose whether to " + + "include that parameter." + reset + "\n" + + bold + cyan + "User input is represented in BOLD text, such as a/AMOUNT" + reset; + displayToUser(message); + } + + + /** + * Prints message to user if there is no description provided in search. + */ + public static void searchEmptyMessage(){ + String result = "Please key in a valid descriptor to search."; + displayToUser(result); + } + + /** + * Displays a message to the console. + * + * @param message The message to be displayed. + */ + public static void showMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/Command.java b/src/main/java/seedu/budgetbuddy/commands/Command.java new file mode 100644 index 0000000000..1ebd2ee960 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/Command.java @@ -0,0 +1,19 @@ +package seedu.budgetbuddy.commands; + +import seedu.budgetbuddy.Ui; + +/** + * Represents the base class for all commands in the BudgetBuddy application. + * This class provides a default execute method, which is meant to be overridden by child classes. + */ +public class Command { + + /** + * Executes the command. + * The default implementation displays a message indicating that the method should be implemented by child classes. + * Child classes should override this method to define specific command behaviors. + */ + public void execute() { + Ui.displayToUser("to be implemented by child classes"); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/ExitCommand.java b/src/main/java/seedu/budgetbuddy/commands/ExitCommand.java new file mode 100644 index 0000000000..78a6bf046e --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/ExitCommand.java @@ -0,0 +1,35 @@ +package seedu.budgetbuddy.commands; + +import seedu.budgetbuddy.Ui; + +/** + * Represents a command to exit the BudgetBuddy application. + * This command triggers the display of the exit message when executed. + */ +public class ExitCommand extends Command { + + /** + * Constructs an ExitCommand object. + */ + public ExitCommand() { + + } + + /** + * Determines if the given command string matches the exit command. + * + * @param command The command string entered by the user. + * @return true if the command is the exit command, false otherwise. + */ + public static boolean isCommand(String command) { + return command.equals("bye"); + } + + /** + * Executes the exit command by displaying the exit message. + */ + @Override + public void execute() { + Ui.displayExitMessage(); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/HelpCommand.java b/src/main/java/seedu/budgetbuddy/commands/HelpCommand.java new file mode 100644 index 0000000000..e2520802eb --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/HelpCommand.java @@ -0,0 +1,25 @@ +package seedu.budgetbuddy.commands; + +import seedu.budgetbuddy.Ui; + +/** + * Represents a command to display a help message to new users + */ +public class HelpCommand extends Command{ + /** + * Determine if given command is the help command + * @param command Input command from user + * @return true if command matches "help". else, false. + */ + public static boolean isCommand(String command){ + return command.startsWith("help"); + } + + /** + * Executes help command by displaying help message to user. + */ + @Override + public void execute(){ + Ui.displayHelpMessage(); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/IncorrectCommand.java b/src/main/java/seedu/budgetbuddy/commands/IncorrectCommand.java new file mode 100644 index 0000000000..3a32556fa6 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/IncorrectCommand.java @@ -0,0 +1,37 @@ +package seedu.budgetbuddy.commands; + +import seedu.budgetbuddy.Ui; + +/** + * Represents a command that provides feedback to the user when an incorrect command is entered. + * This command displays an appropriate message to inform the user of the error. + */ +public class IncorrectCommand extends Command { + public final String feedbackToUser; + + /** + * Constructs an IncorrectCommand object with the specified feedback message. + * + * @param feedbackToUser The message to be displayed to the user when the command is executed. + */ + public IncorrectCommand(String feedbackToUser) { + this.feedbackToUser = feedbackToUser; + } + + /** + * Executes the incorrect command by displaying the feedback message to the user. + */ + @Override + public void execute () { + Ui.displayToUser(feedbackToUser); + } + + /** + * Retrieves the feedback message for the user. + * + * @return The feedback message as a String. + */ + public String getFeedbackToUser() { + return feedbackToUser; + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/budget/AddBudgetCommand.java b/src/main/java/seedu/budgetbuddy/commands/budget/AddBudgetCommand.java new file mode 100644 index 0000000000..0ca86eb0ec --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/budget/AddBudgetCommand.java @@ -0,0 +1,100 @@ +package seedu.budgetbuddy.commands.budget; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Logger; + +/** + * Represents a command to add a budget for a specific month and year. + */ +public class AddBudgetCommand extends Command { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private double amount; + private YearMonth date; + private Category category; + + /** + * Constructs an AddBudgetCommand with the specified amount and date. + * + * @param amount The amount of the budget to be added. + * @param date The YearMonth representing the month and year for the budget. + */ + public AddBudgetCommand(double amount, YearMonth date, Category category) { + assert amount >= 0 : "Amount must be non-negative"; + assert date != null : "Date cannot be null"; + assert category != null : "Category cannot be null"; + this.amount = amount; + this.date = date; + this.category = category; + } + + /** + * Returns the amount associated with the budget. Useful for unit testing. + * + * @return The amount as a double value. + */ + public double getAmount() { + return amount; + } + + /** + * Returns the date associated with the budget. Useful for unit testing. + * + * @return The date as a YearMonth object. + */ + public YearMonth getDate() { + return date; + } + + /** + * Returns the category associated with the budget. Useful for unit testing. + * + * @return The category as a Category object. + */ + public Category getCategory() { + return category; + } + + /** + * Checks if the given command string starts with "add budget". + * + * @param command The command string entered by the user. + * @return true if the command starts with "add budget", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("add budget"); + } + + /** + * Executes the AddBudgetCommand by checking if a budget already exists for the specified month and year (date). + * If a budget exists, the specified amount is added to the existing budget. + * If no budget exists, a new budget is created and added to the BudgetManager. + */ + @Override + public void execute() { + if (amount < 0) { + throw new IllegalArgumentException("Amount must be non-negative."); + } + + // Check if a budget already exists for the specified date + Budget existingBudget = BudgetManager.getBudget(date); + + if (existingBudget != null) { + existingBudget.addAmount(category, amount); + Ui.displayBudgetTransactionMessage(existingBudget.toString(), BudgetManager.getNumberOfBudgets()); + LOGGER.info("Updated existing budget for date: " + date + " with amount: " + amount); + } else { + Budget budget = new Budget(date); + BudgetManager.addBudget(budget); + budget.addAmount(category, amount); + Ui.displayBudgetTransactionMessage(budget.toString(), BudgetManager.getNumberOfBudgets()); + LOGGER.info("Added new budget for date: " + date + " with amount: " + amount); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommand.java b/src/main/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommand.java new file mode 100644 index 0000000000..374002c383 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommand.java @@ -0,0 +1,85 @@ +package seedu.budgetbuddy.commands.budget; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Logger; + +/** + * Represents a command to deduct a budget for a specific month and year. + */ +public class DeductBudgetCommand extends Command { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private double amount; + private YearMonth date; + private Category category; + + /** + * Constructs a DeductBudgetCommand with the specified amount and date. + * + * @param amount The amount of the budget to be deducted. + * @param date The YearMonth representing the month and year for the budget. + */ + public DeductBudgetCommand(double amount, YearMonth date, Category category) { + assert amount >= 0 : "Amount must be non-negative"; + assert date != null : "Date cannot be null"; + assert category != null : "Category cannot be null"; + this.amount = amount; + this.date = date; + this.category = category; + } + + /** + * Returns the amount associated with the budget. Useful for unit testing. + * + * @return The amount as a double value. + */ + public double getAmount() { + return amount; + } + + /** + * Returns the date associated with the budget. Useful for unit testing. + * + * @return The date as a YearMonth object. + */ + public YearMonth getDate() { + return date; + } + + /** + * Returns the category associated with the budget. Useful for unit testing. + * + * @return The category as a Category object. + */ + public Category getCategory() { + return category; + } + + /** + * Checks if the given command string starts with "deduct budget". + * + * @param command The command string entered by the user. + * @return true if the command starts with "deduct budget", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("deduct budget"); + } + + /** + * Executes the DeductBudgetCommand by checking if a budget already exists for the specified month and year. + * If a budget exists, the specified amount is deducted from the existing budget. + * If no budget exists, the method simply returns, as the exception is handled in the DeductBudgetValidator. + */ + @Override + public void execute() { + Budget existingBudget = BudgetManager.getBudget(date); + assert existingBudget != null; + existingBudget.deductAmount(category, amount); + LOGGER.info("Deducted " + amount + " from budget for date: " + date); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/budget/ListBudgetCommand.java b/src/main/java/seedu/budgetbuddy/commands/budget/ListBudgetCommand.java new file mode 100644 index 0000000000..7e1450be3d --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/budget/ListBudgetCommand.java @@ -0,0 +1,50 @@ +package seedu.budgetbuddy.commands.budget; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.budget.BudgetManager; + +import java.time.YearMonth; + +/** + * Represents a command to list all budgets managed by the BudgetManager. + * This command retrieves and displays the current budgets for the user. + */ +public class ListBudgetCommand extends Command { + private YearMonth date; + + /** + * Constructs a ListBudgetCommand with an optional YearMonth. + * + * @param date The YearMonth for which budgets should be listed, or null for all budgets. + */ + public ListBudgetCommand(YearMonth date) { + this.date = date; + } + + /** + * Retrieves the date associated with this command. Useful for unit testing. + * + * @return The specified date, or null if no specific date is set. + */ + public YearMonth getDate() { + return date; + } + + /** + * Checks if the provided command matches the command to list budget. + * + * @param command The command to be checked. + * @return True if the command matches "list budget", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("list budgets"); + } + + /** + * Executes the command to list all budgets by invoking the BudgetManager's method. + */ + @Override + public void execute() { + BudgetManager.listBudgets(date); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/budget/ListRemainingBudgetCommand.java b/src/main/java/seedu/budgetbuddy/commands/budget/ListRemainingBudgetCommand.java new file mode 100644 index 0000000000..caf31a4f11 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/budget/ListRemainingBudgetCommand.java @@ -0,0 +1,28 @@ +package seedu.budgetbuddy.commands.budget; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.budget.RemainingBudgetManager; + +/** + * Represents a command to list all budgets managed by the BudgetManager. + * This command retrieves and displays the current budgets for the user. + */ +public class ListRemainingBudgetCommand extends Command { + /** + * Checks if the provided command matches the command to list budget. + * + * @param command The command to be checked. + * @return True if the command matches "list budget", false otherwise. + */ + public static boolean isCommand(String command) { + return command.equals("list remaining budget"); + } + + /** + * Executes the command to list all budgets by invoking the BudgetManager's method. + */ + @Override + public void execute() { + new RemainingBudgetManager().listRemainingBudgets(); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/AddExpenseCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/AddExpenseCommand.java new file mode 100644 index 0000000000..19693a34da --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/AddExpenseCommand.java @@ -0,0 +1,89 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.time.LocalDate; + +/** + * Represents a command to add an expense in the BudgetBuddy application. + * This command will create a new expense with the given description, amount, date, and category + * and add it to the expense manager. + */ +public class AddExpenseCommand extends Command { + private String description; + private double amount; + private LocalDate date; + private Category category; + + /** + * Constructs an AddExpenseCommand with the specified description, amount, date, and category. + * + * @param description A description of the expense. + * @param amount The amount of the expense. + * @param date The date of the expense. + * @param category The category of the expense. + */ + public AddExpenseCommand(String description, double amount, LocalDate date, Category category) { + this.description = description; + this.amount = amount; + this.date = date; + this.category = category; + } + + /** + * Checks if the given command string starts with "add expense". + * + * @param command The command string entered by the user. + * @return true if the command starts with "add expense", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("add expense"); + } + + /** + * Executes the AddExpenseCommand by adding the specified expense to the expense manager. + */ + @Override + public void execute() { + ExpenseManager.addExpense(new Expense(description, amount, date, category)); + } + + /** + * Gets the description of the expense. + * + * @return The description of the expense. + */ + public String getDescription() { + return description; + } + + /** + * Gets the amount of the expense. + * + * @return The amount of the expense. + */ + public double getAmount() { + return amount; + } + + /** + * Gets the date of the expense. + * + * @return The date of the expense. + */ + public LocalDate getDate() { + return date; + } + + /** + * Gets the category of the expense. + * + * @return The category of the expense. + */ + public Category getCategory() { + return category; + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/BreakdownExpensesCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/BreakdownExpensesCommand.java new file mode 100644 index 0000000000..972d838777 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/BreakdownExpensesCommand.java @@ -0,0 +1,27 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +/** + * Represents a command to breakdown all expenses in terms of category to allow the user to + * know where they are spending their money. + */ +public class BreakdownExpensesCommand extends Command { + /** + * Verifies that the user input starts with "display expenses" + * @param command User input command + * @return true if command starts with "breakdown expenses", false otherwise. + */ + public static boolean isCommand(String command){ + return command.equals("breakdown expenses"); + } + + /** + * Executes the command to display expenses by category breakdown, and prints the output to the user. + */ + public void execute(){ + Ui.displayToUser(ExpenseManager.breakdownExpensesByCategory()); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommand.java new file mode 100644 index 0000000000..dcd59d9644 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommand.java @@ -0,0 +1,40 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +/** + * Represents a command to delete an expense in the BudgetBuddy application. + * This command removes an expense at the specified index from the expense list. + */ +public class DeleteExpenseCommand extends Command { + private final int index; + + /** + * Constructs a DeleteExpenseCommand with the specified index of the expense to be deleted. + * + * @param index The index of the expense to delete. + */ + public DeleteExpenseCommand(int index) { + this.index = index; + } + + /** + * Determines if the given command string matches the delete expense command. + * + * @param command The command string entered by the user. + * @return true if the command is a delete expense command, false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("delete expense"); + } + + /** + * Executes the delete expense command by removing the expense at the specified index + * from the expense list. + */ + @Override + public void execute(){ + ExpenseManager.deleteExpense(index); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/DisplayExpensesForMonthWithCategoriesGraphCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/DisplayExpensesForMonthWithCategoriesGraphCommand.java new file mode 100644 index 0000000000..5dbaaf6d98 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/DisplayExpensesForMonthWithCategoriesGraphCommand.java @@ -0,0 +1,57 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Command for displaying a pie chart of expenses categorized by month. + */ +public class DisplayExpensesForMonthWithCategoriesGraphCommand extends Command { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private YearMonth yearMonth; + + /** + * Constructs a command to display expenses for a specific month with categories. + * + * @param yearMonth The year and month for which to display expenses. + */ + public DisplayExpensesForMonthWithCategoriesGraphCommand(YearMonth yearMonth) { + this.yearMonth = yearMonth; + } + + /** + * Checks if the given command is a display expenses with categories command. + * + * @param command The command string to check. + * @return True if the command starts with "display expenses with categories", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("display expenses with categories"); + } + + /** + * Gets the YearMonth attribute of the class + * + * @return YearMonth attribute + */ + public YearMonth getYearMonth() { + return this.yearMonth; + } + + /** + * Executes the command to display the expenses pie chart for the specified month. + */ + @Override + public void execute(){ + LOGGER.log(Level.INFO, "Displaying expense PieChart"); + Ui.displayToUser("Displaying Expenses PieChart for " + yearMonth.toString()); + ExpenseManager.displayExpensesForMonthWithCategoriesGraph(yearMonth); + } +} + diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/DisplayTotalExpensesCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/DisplayTotalExpensesCommand.java new file mode 100644 index 0000000000..dc5580afae --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/DisplayTotalExpensesCommand.java @@ -0,0 +1,55 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Command for displaying total expenses for a specific year or month. + */ +public class DisplayTotalExpensesCommand extends Command { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private int year; + + /** + * Constructs a command to display expenses for the specified year. + * + * @param year The year for which total expenses are to be displayed. + */ + public DisplayTotalExpensesCommand(int year) { + this.year = year; + } + + /** + * Checks if the given command matches the expected format for displaying monthly expenses. + * + * @param command The command string to check. + * @return true if the command matches the expected format; false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("display monthly expenses"); + } + + /** + * Executes the command to display a graph of total expenses for the year + */ + @Override + public void execute() { + LOGGER.log(Level.INFO, "Displaying expense graph"); + Ui.displayToUser("Displaying expense graph for " + year); + ExpenseManager.displayExpensesOverMonthGraph(year); + } + + /** + * Get function to get the message to user. + * + * @return Message to User + */ + public String getFeedbackToUser() { + return "Displaying expense graph for " + year; + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/EditExpenseCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/EditExpenseCommand.java new file mode 100644 index 0000000000..7a8e30e6a8 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/EditExpenseCommand.java @@ -0,0 +1,118 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.util.LoggerSetup; +import seedu.budgetbuddy.validators.expense.EditExpenseValidator; + +import java.time.LocalDate; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class EditExpenseCommand extends Command { + + private static final Logger LOGGER = LoggerSetup.getLogger(); + private static final double EMPTY_AMOUNT = -1.0; + private static final Category EMPTY_CATEGORY = null; + private static final LocalDate EMPTY_DATE = null; + private static Category category; + private static LocalDate date; + private static double amount; + private Expense expense; + + public EditExpenseCommand(Expense expense) { + this.expense = expense; + } + + /** + * Checks if the provided command matches the command to edit expenses. + * + * @param command The command to be checked. + * @return True if the command matches "edit expenses", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("edit expense"); + } + + /** + * Set date that will be used to update expenses + * + * @param newDate new Expense Date + */ + public static void setDate(LocalDate newDate) { + date = newDate; + } + + /** + * Set amount that will be used to update expenses + * + * @param newAmount new Expense Amount + */ + public static void setAmount(double newAmount) { + amount = newAmount; + } + + /** + * Set category that will be used to update expenses + * + * @param newCategory new Expense Category + */ + public static void setCategory(Category newCategory) { + category = newCategory; + } + + /** + * Execute Command to get fields to edit an expense object + * Ends Command if user input nothing + * Shows Error Text if user input an invalid field + * Else Edits the expense based on input fields + */ + public void execute() { + String editFields = getEditFields(); + LOGGER.log(Level.INFO, "Successfully retrieve edit fields"); + Boolean validInput; + if (editFields.isEmpty()) { + return; + } + validInput = EditExpenseValidator.processSecondCommand(editFields); + if (validInput) { + processEdit(); + } + LOGGER.log(Level.INFO, "Successfully edit expense"); + } + + /** + * Displays Custom Message that informs users to input edit fields + * Then reads in user input. + * + * @return User input EditFields that will be used for editing Expenses {@code String} + */ + public String getEditFields() { + Ui.showMessage("Edit the following fields as follows: Amount: a/, Category: c/, Date: d/\n" + + "You may the exit change menu by pressing enter with no input.\n" + + "Currently Editing Entry:\n" + + expense.toString()); + String editFields = Ui.getUserEditFields(); + return editFields; + } + + /** + * Process which fields to edit based on values stored + * For any field that is not left empty by user, it will update the Expense object. + */ + public void processEdit() { + if (category != EMPTY_CATEGORY) { + expense.editCategory(category); + } + if (date != EMPTY_DATE) { + expense.editDate(date); + } + if (amount != EMPTY_AMOUNT) { + expense.editAmount(amount); + } + Ui.displayToUser("Edited Expense:\n" + expense.toString()); + } + +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/ListExpenseCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/ListExpenseCommand.java new file mode 100644 index 0000000000..1678f5a490 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/ListExpenseCommand.java @@ -0,0 +1,99 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Represents a command that lists all expenses in the expense manager. + * This command retrieves and displays the current expenses for the user. + */ +public class ListExpenseCommand extends Command { + + private static final Logger LOGGER = LoggerSetup.getLogger(); + private Category category; + private YearMonth month; + + /** + * Constructs a ListExpenseCommand with no specified date or category + */ + public ListExpenseCommand() { + this.category = null; + this.month = null; + } + + /** + * Constructs a ListExpenseCommand with valid category and month field + * + * @param category + * @param month + */ + public ListExpenseCommand(Category category, YearMonth month) { + this.category = category; + this.month = month; + } + + /** + * Constructs a ListExpenseCommand with valid month field + * + * @param month + */ + public ListExpenseCommand(YearMonth month) { + this.category = null; + this.month = month; + } + + /** + * Constructs a ListExpenseCommand with valid category field + * + * @param category + */ + public ListExpenseCommand(Category category) { + this.category = category; + this.month = null; + } + + /** + * Checks if the provided command matches the command to list expenses. + * + * @param command The command to be checked. + * @return True if the command matches "list expenses", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("list expenses"); + } + + public Category getCategory() { + return category; + } + + public YearMonth getMonth() { + return month; + } + + /** + * Executes the command to list all expenses by invoking the ExpenseManager's method. + */ + @Override + public void execute() { + if (category == null && month == null) { + LOGGER.log(Level.INFO, "Displaying expenses listed with no Filter"); + ExpenseManager.listExpenses(); + } else if (category == null) { + LOGGER.info("Displaying expenses listed in the Month: " + month); + Ui.displayToUser(ExpenseManager.listExpensesWithDate(month)); + } else if (month == null) { + LOGGER.info("Displaying expenses listed with Category: " + category); + Ui.displayToUser(ExpenseManager.listExpensesWithCategory(category)); + } else { + LOGGER.info("Displaying expenses listed with Category: " + category + " and Month: " + month); + Ui.displayToUser(ExpenseManager.listExpensesWithCategoryAndDate(category,month)); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/expense/SearchExpenseCommand.java b/src/main/java/seedu/budgetbuddy/commands/expense/SearchExpenseCommand.java new file mode 100644 index 0000000000..6e6fb14808 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/expense/SearchExpenseCommand.java @@ -0,0 +1,48 @@ +package seedu.budgetbuddy.commands.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +public class SearchExpenseCommand extends Command { + private String keyword; + + /** + * Constructs SearchExpenseCommand object with empty string as keyword + */ + public SearchExpenseCommand(){ + keyword = ""; + } + + /** + * Constructs SearchExpenseCommand object with keyword set as specified keyword. + * @param keyword + */ + public SearchExpenseCommand(String keyword){ + this.keyword = keyword; + } + + /** + * Processes user input to check if search command is called by user. + * @param command user input + * @return True if command starts with "search", False otherwise + */ + public static boolean isCommand(String command){ + return command.startsWith("search expenses"); + } + + /** + * Executes command to search expense list based on keyword. + */ + public void execute(){ + if (getKeyword().equals("")){ + Ui.searchEmptyMessage(); + } else{ + Ui.displayToUser(ExpenseManager.searchExpenses(getKeyword())); + } + } + + public String getKeyword(){ + return keyword; + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/income/AddIncomeCommand.java b/src/main/java/seedu/budgetbuddy/commands/income/AddIncomeCommand.java new file mode 100644 index 0000000000..150a4a75c3 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/income/AddIncomeCommand.java @@ -0,0 +1,77 @@ +package seedu.budgetbuddy.commands.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.transaction.income.Income; + +import java.time.LocalDate; + +/** + * Represents a command to add an income entry in the BudgetBuddy application. + * This command will create a new income with the given description, amount, and date + * and add it to the IncomeManager. + */ +public class AddIncomeCommand extends Command { + private String description; + private double amount; + private LocalDate date; + + /** + * Constructs an AddIncomeCommand with the specified description, amount, and date. + * + * @param description A description of the income. + * @param amount The amount of the income. + * @param date The date of the income. + */ + public AddIncomeCommand(String description, double amount, LocalDate date) { + this.description = description; + this.amount = amount; + this.date = date; + } + + /** + * Checks if the given command string starts with "add income". + * + * @param command The command string entered by the user. + * @return true if the command starts with "add income", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("add income"); + } + + /** + * Executes the AddIncomeCommand by adding the specified income to the IncomeManager. + */ + @Override + public void execute() { + IncomeManager.addIncome(new Income(description, amount, date)); + } + + /** + * Gets the description of the item. + * + * @return The description as a String. + */ + public String getDescription() { + return this.description; + } + + /** + * Gets the amount associated with the item. + * + * @return The amount as a double. + */ + public double getAmount() { + return this.amount; + } + + /** + * Gets the date associated with the item. + * + * @return The date as a LocalDate object. + */ + public LocalDate getDate() { + return this.date; + } + +} diff --git a/src/main/java/seedu/budgetbuddy/commands/income/DeleteIncomeCommand.java b/src/main/java/seedu/budgetbuddy/commands/income/DeleteIncomeCommand.java new file mode 100644 index 0000000000..8bef6422c3 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/income/DeleteIncomeCommand.java @@ -0,0 +1,47 @@ +package seedu.budgetbuddy.commands.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +/** + * Represents a command to delete an income in the BudgetBuddy application. + * This command removes an income at the specified index from the income list. + */ +public class DeleteIncomeCommand extends Command { + private final int index; + + /** + * Constructs a DeleteIncomeCommand with the specified index of the income to be deleted. + * + * @param index The index of the income to delete. + */ + public DeleteIncomeCommand(int index) { + this.index = index; + } + + /** + * Determines if the given command string matches the delete income command. + * + * @param command The command string entered by the user. + * @return true if the command is a delete income command, false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("delete income"); + } + + /** + * Executes the delete income command by removing the income at the specified index + * from the income list. + */ + @Override + public void execute(){ + IncomeManager.deleteIncome(index); + } + + /** + * Returns the index of income to delete. + */ + public int getIndex() { + return index; + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommand.java b/src/main/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommand.java new file mode 100644 index 0000000000..543aca06c0 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommand.java @@ -0,0 +1,49 @@ +package seedu.budgetbuddy.commands.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.income.IncomeSpent; + +import java.time.YearMonth; + +/** + * Represents a command to display the percentage of income spent for a specified month. + */ +public class DisplayIncomeSpentCommand extends Command { + private YearMonth month; + + /** + * Constructs a DisplayIncomeSpentCommand with the specified month. + * + * @param month The month for which to display the income spent percentage. + */ + public DisplayIncomeSpentCommand(YearMonth month) { + this.month = month; + } + + /** + * Retrieves the month associated with this command. Useful for unit testing. + * + * @return The specified month of the income spent. + */ + public YearMonth getMonth() { + return month; + } + + /** + * Checks if the given command is a display income spent command. + * + * @param command The command string to check. + * @return true if the command starts with "display income spent"; false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("display income spent"); + } + + /** + * Executes the command to display the percentage of income spent for the specified month. + */ + @Override + public void execute() { + IncomeSpent.displaySpentPercentage(month); + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/income/EditIncomeCommand.java b/src/main/java/seedu/budgetbuddy/commands/income/EditIncomeCommand.java new file mode 100644 index 0000000000..2e56419ec9 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/income/EditIncomeCommand.java @@ -0,0 +1,104 @@ +package seedu.budgetbuddy.commands.income; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.util.LoggerSetup; +import seedu.budgetbuddy.validators.income.EditIncomeValidator; + +import java.time.LocalDate; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class EditIncomeCommand extends Command { + + private static final Logger LOGGER = LoggerSetup.getLogger(); + private static final double EMPTY_AMOUNT = -1.0; + private static final LocalDate EMPTY_DATE = null; + private static LocalDate date; + private static double amount; + private Income income; + + + public EditIncomeCommand(Income income) { + this.income = income; + } + + /** + * Checks if the provided command matches the command to edit incomes. + * + * @param command The command to be checked. + * @return True if the command matches "edit incomes", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("edit income"); + } + + /** + * Set date that will be used to update income + * + * @param newDate new Income Date + */ + public static void setDate(LocalDate newDate) { + date = newDate; + } + + /** + * Set amount that will be used to update income + * + * @param newAmount new Income Amount + */ + public static void setAmount(double newAmount) { + amount = newAmount; + } + + /** + * Execute Command to get fields to edit an income object + * Ends Command if user input nothing + * Shows Error Text if user input an invalid field + * Else Edits the income based on input fields + */ + public void execute() { + String editFields = getEditFields(); + LOGGER.log(Level.INFO, "Successfully retrieve edit fields"); + Boolean validInput; + if(editFields.isEmpty()) { + return; + } + validInput = EditIncomeValidator.processSecondCommand(editFields); + if (validInput) { + processEdit(); + } + LOGGER.log(Level.INFO, "Successfully edit income"); + } + + /** + * Displays Custom Message that informs users to input edit fields + * Then reads in user input. + * + * @return User input EditFields that will be used for editing Income {@code String} + */ + public String getEditFields() { + Ui.showMessage("Edit the following fields as follows: Amount: a/, Date: d/\n" + + "You may the exit change menu by pressing enter with no input.\n" + + "Currently Editing Entry:\n" + + income.toString()); + String editFields = Ui.getUserEditFields(); + return editFields; + } + + /** + * Process which fields to edit based on values stored + * For any field that is not left empty by user, it will update the Income object. + */ + public void processEdit() { + if (date != EMPTY_DATE) { + income.editDate(date); + } + if (amount != EMPTY_AMOUNT) { + income.editAmount(amount); + } + Ui.displayToUser("Edited Income:\n" + income.toString()); + } + +} diff --git a/src/main/java/seedu/budgetbuddy/commands/income/ListIncomeCommand.java b/src/main/java/seedu/budgetbuddy/commands/income/ListIncomeCommand.java new file mode 100644 index 0000000000..3000d40fc6 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/income/ListIncomeCommand.java @@ -0,0 +1,53 @@ +package seedu.budgetbuddy.commands.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.time.YearMonth; + +/** + * Represents a command that lists all incomes in the income manager. + * This command retrieves and displays the current incomes for the user. + */ +public class ListIncomeCommand extends Command { + + private YearMonth month; + + /** + * Constructs a ListIncomeCommand with no specified date or category + */ + public ListIncomeCommand() { + this.month = null; + } + + /** + * Constructs a ListIncomeCommand with valid month field + * + * @param month + */ + public ListIncomeCommand(YearMonth month) { + this.month = month; + } + + /** + * Checks if the provided command matches the command to list incomes. + * + * @param command The command to be checked. + * @return True if the command matches "list incomes", false otherwise. + */ + public static boolean isCommand(String command) { + return command.startsWith("list incomes"); + } + + /** + * Executes the command to list all incomes by invoking the IncomeManager's method. + */ + @Override + public void execute() { + if (month == null) { + IncomeManager.listIncomes(); + } else { + IncomeManager.listIncomeWithMonth(month); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/commands/saving/DisplaySavingsCommand.java b/src/main/java/seedu/budgetbuddy/commands/saving/DisplaySavingsCommand.java new file mode 100644 index 0000000000..5bc6217359 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/commands/saving/DisplaySavingsCommand.java @@ -0,0 +1,40 @@ +package seedu.budgetbuddy.commands.saving; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.transaction.saving.SavingsManager; + +/** + * Represents a command that displays the savings of the user. + */ +public class DisplaySavingsCommand extends Command{ + private boolean byMonth; + + /** + * Constructs a new DisplaySavingsCommand object. + * @param byMonth true if the user wishes to display savings by month, otherwise false. + */ + public DisplaySavingsCommand(boolean byMonth) { + this.byMonth = byMonth; + } + + /** + * Checks if the user command matches the display savings command. + * @param command Command by user. + * @return true if command starts with "display savings", otherwise false. + */ + public static boolean isCommand(String command){ + return command.startsWith("display savings"); + } + + /** + * Executes command to display savings to user, either by month or in total. + */ + public void execute(){ + if (byMonth){ + Ui.displayToUser(SavingsManager.displayTotalSavingsByMonth()); + } else{ + Ui.displayToUser(SavingsManager.displayTotalSavings()); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/exceptions/BudgetBuddyException.java b/src/main/java/seedu/budgetbuddy/exceptions/BudgetBuddyException.java new file mode 100644 index 0000000000..a039e3c7da --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/exceptions/BudgetBuddyException.java @@ -0,0 +1,16 @@ +package seedu.budgetbuddy.exceptions; + +import seedu.budgetbuddy.Ui; + +/** + * Represents a custom exception for errors specific to BudgetBuddy application. + */ +public class BudgetBuddyException extends Exception { + + /** + * Constructs a new BudgetBuddyException with the specified detail message. + */ + public BudgetBuddyException(String message) { + super(Ui.SEPARATOR + "\n" + message + "\n" + Ui.SEPARATOR); + } +} diff --git a/src/main/java/seedu/budgetbuddy/graphs/ExpensesCategoryPieChart.java b/src/main/java/seedu/budgetbuddy/graphs/ExpensesCategoryPieChart.java new file mode 100644 index 0000000000..2bc913c9f4 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/graphs/ExpensesCategoryPieChart.java @@ -0,0 +1,94 @@ +package seedu.budgetbuddy.graphs; + +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; +import org.knowm.xchart.SwingWrapper; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; + +import javax.swing.JFrame; +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static seedu.budgetbuddy.transaction.expense.ExpenseManager.getExpenses; +import static seedu.budgetbuddy.transaction.expense.ExpenseManager.getYearMonthFromDate; + +/** + * Generates and displays a pie chart of expenses sliced by category. + */ +public class ExpensesCategoryPieChart { + + /** + * Builds a map of total expenses grouped by category for a given month. + * + * @param yearMonth The year and month for which to calculate expenses. + * @return A map where the keys are categories and the values are the total expenses for that category. + */ + public static Map expensesByCategoryMapBuilder(YearMonth yearMonth) { + Map expensesByCategoryMap = new HashMap<>(); + + for (Category category : Category.values()) { + double categoryTotal = getTotalExpensesForMonthWithCategories(yearMonth, category); + expensesByCategoryMap.put(category, categoryTotal); + } + + return expensesByCategoryMap; + } + + /** + * Displays a pie chart of expenses by category for a specific month. + * + * @param yearMonth The year and month for which to display the expenses. + * @param expensesByCategoryMap A map containing the total expenses for each category. + */ + public static void displayExpenseByCategoryPieChart(YearMonth yearMonth, Map + expensesByCategoryMap) { + // Initialize the pie chart + PieChart pieChart = new PieChartBuilder() + .width(800) + .height(600) + .title("Expenses by Category for " + yearMonth) + .build(); + + // Set visibility of the legend and labels + pieChart.getStyler().setLegendVisible(true); + pieChart.getStyler().setLabelsVisible(true); + + // Add each category to the chart with the expense amount shown on each slice + expensesByCategoryMap.forEach((category, expense) -> { + String label = category.name() + " " + expense.toString(); + pieChart.addSeries(label, expense); + }); + + //Display the chart in window + SwingWrapper swingWrapper = new SwingWrapper<>(pieChart); + JFrame frame = swingWrapper.displayChart(); + frame.setTitle("BudgetBuddy"); + + // Ensure that closing the window does not end the program + javax.swing.SwingUtilities.invokeLater(() -> frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)); + } + + /** + * Calculates the total expenses for a specific month and category. + * + * @param yearMonth The year and month for which to calculate expenses. + * @param category The category for which to calculate the total expenses. + * @return The total amount of expenses for the specified month and category. + */ + public static double getTotalExpensesForMonthWithCategories(YearMonth yearMonth, Category category) { + ArrayList expensesOverMonthArray = getExpenses(); + double totalAmount = 0.0; + + for (Expense expense : expensesOverMonthArray) { + YearMonth expenseYearMonth = getYearMonthFromDate(expense.getDate()); + + if (expenseYearMonth.equals(yearMonth) && category.equals(expense.getCategory())) { + totalAmount += expense.getAmount(); + } + } + return totalAmount; + } +} diff --git a/src/main/java/seedu/budgetbuddy/graphs/ExpensesOverMonthGraph.java b/src/main/java/seedu/budgetbuddy/graphs/ExpensesOverMonthGraph.java new file mode 100644 index 0000000000..1debf638dc --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/graphs/ExpensesOverMonthGraph.java @@ -0,0 +1,77 @@ +package seedu.budgetbuddy.graphs; + +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.XYChart; +import org.knowm.xchart.XYChartBuilder; +import seedu.budgetbuddy.transaction.expense.Expense; + +import javax.swing.JFrame; +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A class that generates and displays a graph showing expenses over months in a given year. + */ +public class ExpensesOverMonthGraph { + + /** + * Builds a map of YearMonth to total expenses for that month. + * It accumulates expenses into their respective months. + * + * @param expenses An ArrayList of Expense objects. + * @return A map where the keys are YearMonth objects and the values are total expenses for that month. + */ + public static Map monthMapBuilder(ArrayList expenses) { + Map monthlyExpenseMap = new HashMap<>(); + + for (Expense expense : expenses) { + YearMonth month = YearMonth.from(expense.getDate()); // Correctly get YearMonth + double amount = expense.getAmount(); + + // Accumulate the expense amounts for each month + monthlyExpenseMap.put(month, monthlyExpenseMap.getOrDefault(month, 0.0) + amount); + } + return monthlyExpenseMap; + } + + /** + * Prints a chart that displays expenses for each month in the specified year. + * The chart is displayed using Swing and is set to close without terminating the program. + * + * @param monthlyExpenseMap A map containing YearMonth as keys and total expenses as values. + * @param year The year for which the expenses will be displayed. + */ + public static void chartPrinter(Map monthlyExpenseMap, int year) { + // Create a list to hold all months in the year + List xAxis = new ArrayList<>(); + List yAxis = new ArrayList<>(); + + for (int month = 1; month <= 12; month++) { + YearMonth yearMonth = YearMonth.of(year, month); // Use a fixed year for the chart + double amount = monthlyExpenseMap.getOrDefault(yearMonth, 0.0); // Get the amount or zero if no expenses + xAxis.add((double) month); // Add Month to axis + yAxis.add(amount); // Add corresponding expense amount to axis + } + + // Build the chart + XYChart expensesChart = new XYChartBuilder().width(800).height(600) + .title("Expenses Over Month Graph") + .xAxisTitle("Month") + .yAxisTitle("Expenses") + .build(); + + expensesChart.addSeries("Monthly Expenses", xAxis, yAxis); + + //Display the chart + SwingWrapper swingWrapper = new SwingWrapper<>(expensesChart); + JFrame frame = swingWrapper.displayChart(); + frame.setTitle("BudgetBuddy"); + + // Ensure that closing the window does not end the program + javax.swing.SwingUtilities.invokeLater(() -> frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)); + } +} + diff --git a/src/main/java/seedu/budgetbuddy/transaction/Category.java b/src/main/java/seedu/budgetbuddy/transaction/Category.java new file mode 100644 index 0000000000..83d34c367c --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/Category.java @@ -0,0 +1,25 @@ +package seedu.budgetbuddy.transaction; + +/** + * Represents the different categories of expenses in the budget management system. + */ +public enum Category { + /** Represents food-related expenses. */ + FOOD, + + /** Represents transportation-related expenses. */ + TRANSPORT, + + /** Represents entertainment-related expenses. */ + ENTERTAINMENT, + + /** Represents education-related expenses. */ + EDUCATION, + + /** Represents utility-related expenses, such as electricity and water bills. */ + UTILITIES, + + /** Represents miscellaneous expenses that do not fall into other categories. */ + OTHERS, + +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/Transaction.java b/src/main/java/seedu/budgetbuddy/transaction/Transaction.java new file mode 100644 index 0000000000..e42d6284e0 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/Transaction.java @@ -0,0 +1,47 @@ +package seedu.budgetbuddy.transaction; + +import java.time.LocalDate; + +/** + * The {@code Transaction} class represents a financial transaction. + * It includes details about the transaction description, amount, and date. + */ +public class Transaction { + protected String description; + protected double amount; + protected LocalDate date; + + /** + * Constructs a Transaction with the specified description, amount, and date. + * + * @param description A brief description of the transaction. + * @param amount The amount of the transaction. + * @param date The date of the transaction. + */ + public Transaction(String description, double amount, LocalDate date) { + this.description = description; + this.amount = amount; + this.date = date; + } + + /** + * Returns a string representation of the transaction, including its description, amount, and date. + * + * @return A formatted string with the transaction details. + */ + public String toString() { + String output = ""; + output += "Description: " + description; + output += " Amount: " + String.format("%.2f", amount); + output += " Date: " + date; + return output; + } + + public void editAmount(double amount) { + this.amount = amount; + } + + public void editDate(LocalDate date) { + this.date = date; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/budget/Budget.java b/src/main/java/seedu/budgetbuddy/transaction/budget/Budget.java new file mode 100644 index 0000000000..ad00fb8bc8 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/budget/Budget.java @@ -0,0 +1,167 @@ +package seedu.budgetbuddy.transaction.budget; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.transaction.Category; + +import java.time.YearMonth; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a budget for a specific month and year. + * A budget tracks a specified amount of money for each category and calculates the total budget for the month. + * Provides methods to add category-specific amounts, adjust the budget, and retrieve details. + */ +public class Budget { + + private double amount; + private YearMonth date; + private Map categoryBudgets; + private double totalMonthlyBudget; + + /** + * Constructs a Budget object with the specified amount and date. + * + * @param date The YearMonth representing the month and year for the budget. + */ + public Budget(YearMonth date) { + assert amount >= 0 : "Initial amount cannot be negative"; + this.date = date; + this.categoryBudgets = new HashMap<>(); + } + + /** + * Constructs a Budget object by copying another Budget object. + * + * @param other The Budget object to copy from. + */ + public Budget(Budget other) { + this.amount = other.amount; // Copy the original amount + this.date = other.date; // Copy the date + this.categoryBudgets = new HashMap<>(other.categoryBudgets); // Deep copy of the category budgets + this.totalMonthlyBudget = other.totalMonthlyBudget; // Copy the total monthly budget + } + + + /** + * Adds or updates the budget amount for a specific category. + * If the category already has a budget, the new amount is added to the existing amount. + * + * @param category The category to which the amount should be allocated. + * @param amount The amount to be added for the specified category. + */ + public void addAmount(Category category, double amount) { + categoryBudgets.put(category, categoryBudgets.getOrDefault(category, 0.0) + amount); + updateTotalBudget(); + } + + /** + * Deducts an amount from the budget for a specific category. + * If the deducted amount brings the category's budget to zero or below, it is removed. + * + * @param category The category from which the amount should be deducted. + * @param deductedAmount The amount to be deducted. + */ + public void deductAmount(Category category, double deductedAmount) { + assert deductedAmount >= 0 : "Amount to deduct cannot be negative"; + double currentAmount = categoryBudgets.getOrDefault(category, 0.0); + + // Deduct the amount or remove the category if the budget goes to zero or below + if (currentAmount - deductedAmount <= 0) { + categoryBudgets.remove(category); + } else { + categoryBudgets.put(category, currentAmount - deductedAmount); + } + if (categoryBudgets.isEmpty()) { + BudgetManager.deleteBudget(this); + } else { + updateTotalBudget(); + Ui.displayBudgetTransactionMessage(toString(), BudgetManager.getNumberOfBudgets()); + } + } + + /** + * Deducts an amount from the budget for a specific category. + * This is used by RemainingBudgetManager + * + * @param category The category from which the amount should be deducted. + * @param deductedAmount The amount to be deducted. + */ + public void deductExpense(Category category, double deductedAmount) { + assert deductedAmount >= 0 : "Amount to deduct cannot be negative"; + double currentAmount = categoryBudgets.getOrDefault(category, 0.0); + + // Deduct the amount and allow the category to go negative + categoryBudgets.put(category, currentAmount - deductedAmount); + + // Update the total budget + updateTotalBudget(); + } + + /** + * Updates the total monthly budget by summing up all the category budgets. + */ + private void updateTotalBudget() { + totalMonthlyBudget = categoryBudgets.values().stream().mapToDouble(Double::doubleValue).sum(); + } + + /** + * Retrieves the total monthly budget, which is the sum of all category budgets for the month. + * + * @return The total budget for the month. + */ + public double getTotalMonthlyBudget() { + return totalMonthlyBudget; + } + + /** + * Retrieves the Hashmap of category budgets. + * + * @return A Map where the keys are Category objects and the values represent the budget amounts for each category. + */ + public Map getCategoryBudgets() { + return categoryBudgets; + } + + /** + * Retrieves the budget amount for a specific category. + * + * @param category The category for which the budget amount is to be retrieved. + * @return The budget amount for the given category, or 0.0 if no budget + * has been set for the specified category. + */ + public double getCategoryBudgetAmount(Category category) { + return categoryBudgets.getOrDefault(category, 0.0); + } + + /** + * Gets the date of the budget. + * + * @return The YearMonth representing the budget date. + */ + public YearMonth getDate() { + return date; + } + + /** + * Returns a string representation of the budget, including its amount and date. + * + * @return A string in the format "Amount: {amount} Date: {date}". + */ + @Override + public String toString() { + String output = "Total Monthly Budget: " + String.format("%.2f", totalMonthlyBudget); + output += " Date: " + date + "\n"; + + // Sort categoryBudgets by key (category name) and build the string with rounded budget values + String sortedCategories = categoryBudgets.entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) // Sort by category names + .map(entry -> entry.getKey() + "=" + String.format("%.2f", entry.getValue())) + .reduce((c1, c2) -> c1 + ", " + c2) // Join entries with ", " + .orElse(""); // Handle the case when the map is empty + + output += " Category: {" + sortedCategories + "}"; + return output; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/budget/BudgetManager.java b/src/main/java/seedu/budgetbuddy/transaction/budget/BudgetManager.java new file mode 100644 index 0000000000..692f95ee3d --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/budget/BudgetManager.java @@ -0,0 +1,176 @@ +package seedu.budgetbuddy.transaction.budget; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * Manages the budgets for different months and years. + * Provides methods to add, retrieve, and manage multiple budgets. + */ +public class BudgetManager { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private static int numberOfBudgets = 0; + private static ArrayList budgets = new ArrayList<>(); + private static BudgetManager instance; + + /** + * Construct a BudgetManager of array content incomes + */ + public BudgetManager(){ + + } + + /** + * Construct a BudgetManager of array content incomes + * + * @param budgets The content to be instantiated + * @param numberOfBudgets The initial count of budgets managed by BudgetManager. + */ + public BudgetManager(ArrayList budgets, int numberOfBudgets){ + BudgetManager.budgets = budgets; + BudgetManager.numberOfBudgets = numberOfBudgets; + } + + /** + * Adds a new budget to the list and increments the total number of budgets. + * Displays a message indicating that a new budget has been added. + * + * @param budget The Budget object to be added. + */ + public static void addBudget(Budget budget) { + assert budget != null : "Budget to be added cannot be null"; + budgets.add(budget); + numberOfBudgets++; + LOGGER.info("Added budget: " + budget); + } + + /** + * Deletes budget from the list and decrements the total number of budgets. + * Displays a message indicating that a budget is deleted. + * + * @param budget The Budget object to be added. + */ + public static void deleteBudget(Budget budget) { + assert budget != null : "Budget to be deleted cannot be null"; + budgets.remove(budget); + numberOfBudgets--; + LOGGER.info("Deleted budget: " + budget.getDate()); + Ui.displayBudgetDeletedMessage(budget.getDate(), numberOfBudgets); + } + + /** + * Returns the current number of budgets. + * + * @return The total number of budgets. + */ + public static int getNumberOfBudgets() { + return numberOfBudgets; + } + + /** + * Retrieves a budget for the specified YearMonth date. + * + * @param date The YearMonth representing the month and year for the budget. + * @return The existing Budget for the specified date, or null if no budget exists. + */ + public static Budget getBudget(YearMonth date) { + assert date != null : "Date cannot be null"; + for (Budget budget : budgets) { + if (budget.getDate().equals(date)) { + LOGGER.info("Retrieved budget for date: " + date); + return budget; + } + } + LOGGER.info("No budget found for date: " + date); + return null; // No budget found for the specified date + } + + /** + * Lists all or specified budgets managed by the manager. + * Displays each budget with its corresponding number. + */ + public static void listBudgets(YearMonth date) { + if (budgets.isEmpty()) { + Ui.displayToUser("No budgets recorded."); + return; + } + + String result = ""; + + // Sort the budgets by YearMonth in descending order + budgets.sort((b1, b2) -> b2.getDate().compareTo(b1.getDate())); + + if (date == null) { + LOGGER.info("No date specified for listing budget."); + + result += "Listing up to the 12 most recent budgets:\n"; + + int entriesToDisplay = Math.min(budgets.size(), 12); + for (int counter = 1; counter <= entriesToDisplay; counter++) { + Budget budget = budgets.get(counter - 1); + result += counter + ". " + budget.toString(); + if (counter < entriesToDisplay) { + result += "\n"; + } + } + } else { + // Assume validator guarantees date is valid + LOGGER.info("Listing budgets for date: " + date); + + Budget budget = getBudget(date); + + if (budget != null) { + result += "Here is the budget for the specified month:\n"; + result += budget.toString(); + } else { + result += "No budget found for date: " + date; + } + } + Ui.displayToUser(result); + } + + /** + * A get-function to obtain the information in the current Budget List. + * + * @return return the budget ArrayList + */ + public static ArrayList getBudgets() { + return budgets; + } + + /** + * Resets the state of the BudgetManager by clearing all budgets and + * setting the total number of budgets to zero. + *

+ * This method is used for unit testing, ensuring that each test + * starts with a clean slate and does not retain any state from + * previous tests. + *

+ */ + public static void reset() { + numberOfBudgets = 0; + budgets.clear(); + } + + /** + * Provides a global point of access to the single instance of the BudgetManager class. + *

+ * This method ensures that only one instance of BudgetManager exists throughout the application's lifetime. + * If an instance does not exist, it creates a new one. Otherwise, it returns the existing instance. + * This method is used for unit testing for Storage to ensure that stored values are correct. + *

+ * + * @return The single instance of the BudgetManager class. + */ + public static BudgetManager getInstance() { + if (instance == null) { + instance = new BudgetManager(budgets, numberOfBudgets); // Pass static fields to constructor + } + return instance; + } + +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManager.java b/src/main/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManager.java new file mode 100644 index 0000000000..914e2f5da7 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManager.java @@ -0,0 +1,190 @@ +package seedu.budgetbuddy.transaction.budget; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * Manages the remaining budgets after expenses have been deducted from the budgets. + * Initializes the remaining budgets based on the current expenses and provides functionality + * to retrieve and list remaining budgets. + */ +public class RemainingBudgetManager { + + /** Logger to record events and operations for RemainingBudgetManager. */ + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** List of budgets after expenses have been deducted. */ + private ArrayList remainingBudgets; + + /** + * Constructs a new RemainingBudgetManager. Initializes the remaining budgets by copying + * the existing budgets and deducting the appropriate expenses. + */ + public RemainingBudgetManager() { + remainingBudgets = new ArrayList<>(); + copyBudgetManager(); + ArrayList expenses = ExpenseManager.getExpenses(); + deductExpensesFromBudget(expenses); + LOGGER.info("Remaining budgets initialized and updated after deductions."); + } + + /** + * Deducts the expenses from the existing budgets based on the list of expenses. + * For each expense, it attempts to find a matching budget for the given date + * and category, and deducts the expense amount from the respective budget. + * + * @param expenses The list of expenses to be deducted. + */ + private void deductExpensesFromBudget(ArrayList expenses) { + for (Expense expense : expenses) { + YearMonth expenseDate = YearMonth.from(expense.getDate()); + Category expenseCategory = expense.getCategory(); + double expenseAmount = expense.getAmount(); + + Budget matchingBudget = null; + matchingBudget = searchForGivenBudget(expenseDate, matchingBudget); + matchingBudget = createNewBudget(matchingBudget, expenseDate); + matchingBudget.deductExpense(expenseCategory, expenseAmount); + + LOGGER.info("Deducted " + expenseAmount + " from budget for " + expenseDate + + " in category " + expenseCategory); + } + } + + /** + * Creates a new budget for a given date if no matching budget is found. + * + * @param matchingBudget The existing matching budget, or null if no budget exists. + * @param expenseDate The date (YearMonth) of the expense. + * @return The new or existing budget for the specified date. + */ + private Budget createNewBudget(Budget matchingBudget, YearMonth expenseDate) { + assert expenseDate != null : "Expense date cannot be null"; + if (matchingBudget == null) { + matchingBudget = new Budget(expenseDate); + remainingBudgets.add(matchingBudget); // Add the new budget to the list + LOGGER.info("Created new budget for " + expenseDate + " with initial amount 0.0"); + } + return matchingBudget; + } + + /** + * Searches for an existing budget that matches the specified date. + * + * @param expenseDate The date (YearMonth) to search for. + * @param matchingBudget The budget to match, initially null. + * @return The matching budget, or null if none is found. + */ + private Budget searchForGivenBudget(YearMonth expenseDate, Budget matchingBudget) { + for (Budget budget : remainingBudgets) { + if (budget.getDate().equals(expenseDate)) { + matchingBudget = budget; + LOGGER.info("Found existing budget for date: " + expenseDate); + break; + } + } + return matchingBudget; + } + + /** + * Copies the existing budgets from the BudgetManager to initialize the remainingBudgets. + */ + private void copyBudgetManager() { + ArrayList budgets = BudgetManager.getBudgets(); + assert budgets != null : "BudgetManager's budgets cannot be null"; + for (Budget budget : budgets) { + remainingBudgets.add(new Budget(budget)); + LOGGER.info("Copied budget: " + budget); + } + } + + /** + * Lists all remaining budgets after expenses have been deducted. + * Displays the result to the user through the UI. + */ + public void listRemainingBudgets() { + if (remainingBudgets.size() == 0) { + Ui.displayToUser("No budgets found"); + LOGGER.info("No budgets found"); + return; + } + String result = "All budgets after expense deductions:"; + for (Budget budget : remainingBudgets) { + result += "\n" + budget; + } + Ui.displayToUser(result); + LOGGER.info("Displayed all remaining budgets to user."); + } + + /** + * Retrieves the remaining budget for a given date and category. + * + * @param date The date (LocalDate) of the expense. + * @param category The category of the expense. + * @return A message indicating the remaining budget, or that no budget was found. + */ + public String getRemainingBudgets(LocalDate date, Category category) { + YearMonth expenseMonth = YearMonth.from(date); + assert date != null : "Date cannot be null"; + assert category != null : "Category cannot be null"; + + String result = findRemainingAmount(category, expenseMonth); + if (result != null) { + return result; + } + + // If no budget is found for the specified date + LOGGER.warning("No budget found for " + expenseMonth + "."); + return "No budget found for " + expenseMonth + "."; + } + + /** + * Searches for the remaining budget for the specified date and category. + * This method checks if a budget exists for the specified month and retrieves the + * remaining amount for the specified category. + * + * @param category The category of the expense. + * @param expenseMonth The month and year of the expense. + * @return A string representing the remaining budget for the specified category, or null if no budget is found. + */ + private String findRemainingAmount(Category category, YearMonth expenseMonth) { + for (Budget budget : remainingBudgets) { + if (budget.getDate().equals(expenseMonth)) { + return processAmount(category, expenseMonth, budget); + } + } + return null; + } + + /** + * Processes the remaining amount for the specified category and budget. + * If the category does not have a budget, it assumes the remaining amount is 0.0. + * + * @param category The category for which the remaining budget is being processed. + * @param expenseMonth The month and year of the expense. + * @param budget The budget from which the remaining amount will be retrieved. + * @return A formatted string showing the remaining budget for the category and any warnings if exceeded. + */ + private static String processAmount(Category category, YearMonth expenseMonth, Budget budget) { + Double remainingAmount = budget.getCategoryBudgets().get(category); + if (remainingAmount == null) { + remainingAmount = 0.00; // If the category does not exist, assume remaining amount is 0 + } + LOGGER.info("Retrieved remaining budget for " + expenseMonth + " in category " + category + + ": " + remainingAmount); + String result = "The remaining budget for " + expenseMonth + " in the " + category + + " category is: " + String.format("%.2f", remainingAmount); + if (remainingAmount < 0) { + result += "\nCaution! You have exceeded your budget!"; + } + return result; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/expense/Expense.java b/src/main/java/seedu/budgetbuddy/transaction/expense/Expense.java new file mode 100644 index 0000000000..47c0a5db03 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/expense/Expense.java @@ -0,0 +1,85 @@ +package seedu.budgetbuddy.transaction.expense; + +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.Transaction; + +import java.time.LocalDate; + +/** + * Represents an expense transaction in the budget management system. + * An expense has a description, amount, date, and category. + */ +public class Expense extends Transaction { + /** The category of the expense. */ + private Category category; + + /** + * Constructs an Expense object with the specified details. + * + * @param description the description of the expense + * @param amount the amount of the expense + * @param date the date of the expense + * @param category the category of the expense + */ + public Expense(String description, double amount, LocalDate date, Category category) { + super(description, amount, date); + this.category = category; + } + + /** + * Returns a string representation of the expense, including its details. + * + * @return a string describing the expense + */ + @Override + public String toString() { + return String.format("Description: %s Amount: %.2f Date: %s Category: %s", + description, amount, date, category); + } + + /** + * Retrieves the description of the transaction. + * + * @return A string representing the description of the transaction. + */ + public String getDescription() { + return description; + } + + /** + * Retrieves the amount of the transaction. + * + * @return A double representing the amount of the transaction. + */ + public double getAmount() { + return amount; + } + + /** + * Retrieves the date of the transaction. + * + * @return A LocalDate representing the date of the transaction. + */ + public LocalDate getDate() { + return date; + } + + /** + * Retrieves the category of the expense. + * + * @return The {@code Category} of the expense. + */ + public Category getCategory() { + return category; + } + + + /** + * Updates the category of the expense. + * + * @param changeCategory The new category for the expense. + */ + public void editCategory(Category changeCategory) { + this.category = changeCategory; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/expense/ExpenseManager.java b/src/main/java/seedu/budgetbuddy/transaction/expense/ExpenseManager.java new file mode 100644 index 0000000000..f4da3589dd --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/expense/ExpenseManager.java @@ -0,0 +1,383 @@ +package seedu.budgetbuddy.transaction.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.graphs.ExpensesCategoryPieChart; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.RemainingBudgetManager; +import seedu.budgetbuddy.util.LoggerSetup; +import seedu.budgetbuddy.graphs.ExpensesOverMonthGraph; + +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +import java.time.LocalDate; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Manages a list of expenses, providing functionalities to add, delete, + * and list expenses, as well as tracking the total number of expenses. + */ +public class ExpenseManager { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private static int numberOfExpenses = 0; + private static ArrayList expenses = new ArrayList<>(); + private static ExpenseManager instance; + + /** + * Construct a ExpenseManager of array content expenses + * + * @param expenses is the content to be instantiated + */ + public ExpenseManager(ArrayList expenses, int numberOfExpenses) { + assert numberOfExpenses >= 0 : "numberOfExpenses should be greater than or equal to 0"; + ExpenseManager.expenses = expenses; + ExpenseManager.numberOfExpenses = numberOfExpenses; + } + + /** + * Construct a ExpenseManager of array content expenses + */ + public ExpenseManager() { + + } + + /** + * Adds a new expense to the manager. + * + * @param expense The expense to be added. + */ + public static void addExpense(Expense expense) { + expenses.add(expense); + numberOfExpenses++; + String budgetRemaining = new RemainingBudgetManager().getRemainingBudgets(expense.getDate() + , expense.getCategory()); + String result = "The following expense transaction has been added:\n" + + expense + '\n' + + "You have " + numberOfExpenses + " expense transaction(s) in total.\n" + budgetRemaining; + Ui.displayToUser(result); + } + + /** + * Load a new expense from storage to the manager. + * + * @param expense The expense to be added. + */ + public static void loadExpense(Expense expense) { + expenses.add(expense); + numberOfExpenses++; + } + + /** + * Deletes an expense from the manager at the specified index. + * + * @param index The index of the expense to be deleted. + */ + public static void deleteExpense(int index) { + numberOfExpenses--; + String result = "The following expense transaction has been deleted:\n" + + expenses.get(index) + '\n' + + "You have " + numberOfExpenses + " expense transaction(s) in total.\n"; + LocalDate date = expenses.get(index).getDate(); + Category category = expenses.get(index).getCategory(); + expenses.remove(index); + String budgetRemaining = new RemainingBudgetManager().getRemainingBudgets(date, category); + result += budgetRemaining; + Ui.displayToUser(result); + } + + /** + * Returns the current number of expenses. + * + * @return The total number of expenses. + */ + public static int getNumberOfExpenses() { + return numberOfExpenses; + } + + /** + * Calculates the total expenses for a specified month. + * + * @param month The month to calculate expenses for. + * @return The total expenses for the month; returns 0.0 if no expense is found. + */ + public static double getMonthlyExpense(YearMonth month) { + double sum = 0; + for (Expense expense : expenses) { + if (month.equals(getYearMonthFromDate(expense.getDate()))) { + sum += expense.getAmount(); + } + } + return sum; + } + + /** + * Lists all the expenses managed by the manager. + * Displays each expense with its corresponding number. + */ + public static void listExpenses() { + if (numberOfExpenses == 0) { + Ui.displayToUser("There are currently no expense entries. Try again after adding an expense entry."); + return; + } + String result = ""; + int counter = 0; + double sumOfExpenses = 0; + for (Expense expense : expenses) { + counter++; + result += counter + ". " + expense.toString() + "\n"; + sumOfExpenses += expense.getAmount(); + } + result += "There are " + counter + " expense(s) in total" + + ", with a sum of $" + String.format("%.2f", sumOfExpenses) + "."; + LOGGER.log(Level.INFO, "Listing {0} expenses", numberOfExpenses); + Ui.displayToUser(result); + } + + /** + * Display all expense that matches with month & category field + * Displays each expense with its corresponding number. + * @param category + * @param month + * @return result String to be displayed to user + */ + public static String listExpensesWithCategoryAndDate(Category category, YearMonth month) { + assert category != null : "category cannot be null"; + assert month != null : "month cannot be null"; + String result = ""; + int counter = 0; + double amount = 0; + String monthInString = month.format(DateTimeFormatter.ofPattern("MMMM yyyy")); + for (Expense expense : expenses) { + if (category.equals(expense.getCategory()) && month.equals(getYearMonthFromDate(expense.getDate()))) { + counter++; + result += counter + ". " + expense.toString() + "\n"; + amount += expense.getAmount(); + } + } + if (result.equals("")) { + result = getEmptyDisplayMessage(); + } else { + result += "Your total expense(s) for " + category + " in " + + monthInString + " are $" + String.format("%.2f", amount); + } + return result; + } + + /** + * Display all expense that matches with category field + * Displays each expense with its corresponding number. + * @param category + * @return result String to be displayed to user + */ + public static String listExpensesWithCategory(Category category) { + assert category != null : "category cannot be null"; + String result = ""; + int counter = 0; + double amount = 0; + for (Expense expense : expenses) { + if (category.equals(expense.getCategory())) { + counter++; + result += counter + ". " + expense.toString() + "\n"; + amount += expense.getAmount(); + } + } + if (result.equals("")) { + result = getEmptyDisplayMessage(); + } else { + result += "Your total expense(s) for " + category + " are $" + String.format("%.2f", amount); + } + return result; + } + + /** + * Display all expense that matches with month field + * Displays each expense with its corresponding number. + * @param month + * @return result String to be displayed to user + */ + public static String listExpensesWithDate(YearMonth month) { + assert month != null : "month cannot be null"; + String result = ""; + int counter = 0; + double amountInMonth = 0; + String monthInString = month.format(DateTimeFormatter.ofPattern("MMMM yyyy")); + for (Expense expense : expenses) { + if (month.equals(getYearMonthFromDate(expense.getDate()))) { + counter++; + result += counter + ". " + expense.toString() + "\n"; + amountInMonth += expense.getAmount(); + } + } + if (result.equals("")) { + result = getEmptyDisplayMessage(); + } else { + result += "Your total expense(s) for " + monthInString + " are $" + String.format("%.2f", amountInMonth); + } + return result; + } + + /** + * Filters expenses with descriptions that contain the keyword(s) provided by user. + * @param keyword + * @return result String displayed to user + */ + public static String searchExpenses(String keyword){ + assert keyword != null: "Keyword should not be null"; + String result = ""; + if (keyword.equals("")) { + result = getEmptyDisplayMessage(); + return result; + } + int counter = 1; + for (Expense expense : expenses) { + if (expense.getDescription().toLowerCase().contains(keyword.toLowerCase())){ + result += counter + ". " + expense.toString() + "\n"; + counter++; + } + } + if (result.equals("")) { + result = getEmptyDisplayMessage(); + } + return result; + } + + /** + * Displays a graph of expenses over the given year. + * + * @param year The year for which the expenses graph is to be displayed. + */ + public static void displayExpensesOverMonthGraph(int year) { + ArrayList expensesOverMonthArray = getExpenses(); + Map monthlyExpensesMap = ExpensesOverMonthGraph.monthMapBuilder(expensesOverMonthArray); + ExpensesOverMonthGraph.chartPrinter(monthlyExpensesMap, year); + } + + /** + * Breaks down all expenses of the user by category. + * @return String that displays total expenses of the user and the amount and percentage of total expenses spent + * per category. + */ + public static String breakdownExpensesByCategory() { + String result = ""; + double totalExpensesFood = 0; + double totalExpensesOthers = 0; + double totalExpensesTransport = 0; + double totalExpensesEntertainment = 0; + double totalExpensesUtilities = 0; + double totalExpensesEducation = 0; + for (Expense expense : expenses) { + switch (expense.getCategory()) { + case FOOD -> totalExpensesFood += expense.getAmount(); + case EDUCATION -> totalExpensesEducation += expense.getAmount(); + case TRANSPORT -> totalExpensesTransport += expense.getAmount(); + case UTILITIES -> totalExpensesUtilities += expense.getAmount(); + case ENTERTAINMENT -> totalExpensesEntertainment += expense.getAmount(); + case OTHERS -> totalExpensesOthers += expense.getAmount(); + default -> LOGGER.warning("Invalid category type detected."); + } + } + assert totalExpensesFood >= 0 : "Total expense for food cannot be negative"; + assert totalExpensesOthers >= 0 : "Total expense for others cannot be negative"; + assert totalExpensesTransport >= 0 : "Total expense for transport cannot be negative"; + assert totalExpensesEntertainment >= 0 : "Total expense for entertainment cannot be negative"; + assert totalExpensesUtilities >= 0 : "Total expense for utilities cannot be negative"; + assert totalExpensesEducation >= 0 : "Total expense for education cannot be negative"; + double totalExpenses = totalExpensesEducation + totalExpensesFood + totalExpensesEntertainment + + totalExpensesTransport + totalExpensesUtilities + totalExpensesOthers; + if (totalExpenses == 0) { + result += "Total expenses: 0. You have not indicated any expense yet."; + } else { + result = "Total expenses: " + totalExpenses + "\n" + "Food: " + totalExpensesFood + "(" + + String.format("%.2f", totalExpensesFood / totalExpenses * 100) + "%)\n" + "Transport: " + + totalExpensesTransport + "(" + String.format("%.2f", totalExpensesTransport / totalExpenses * 100) + + "%)\n" + "Utilities: " + totalExpensesUtilities + "(" + + String.format("%.2f", totalExpensesUtilities / totalExpenses * 100) + "%)\n" + "Entertainment: " + + totalExpensesEntertainment + "(" + + String.format("%.2f", totalExpensesEntertainment / totalExpenses * 100) + "%)\n" + "Education: " + + totalExpensesEducation + "(" + + String.format("%.2f", totalExpensesEducation / totalExpenses * 100) + "%)\n" + "Others: " + + totalExpensesOthers + "(" + String.format("%.2f", totalExpensesOthers / totalExpenses * 100) + + "%)\n"; + } + return result; + } + + /** + * Displays the expenses for a specific month on a PieChart divided by different categories + * + * @param yearMonth The YearMonth object representing the month for which the total expenses are to be displayed. + */ + public static void displayExpensesForMonthWithCategoriesGraph(YearMonth yearMonth) { + Map expensesByCategoryMap = ExpensesCategoryPieChart.expensesByCategoryMapBuilder(yearMonth); + ExpensesCategoryPieChart.displayExpenseByCategoryPieChart(yearMonth, expensesByCategoryMap); + } + + /** + * Extract YearMonth value from date + * @param date + * @return + */ + public static YearMonth getYearMonthFromDate(LocalDate date) { + assert date != null: "Date should not be null"; + return YearMonth.from(date); + } + + /** + * Generates a custom empty Display Expense message + * @return custom display Expense message + */ + public static String getEmptyDisplayMessage() { + return "No expense entry with given parameters found, try again with a different parameter."; + } + + /** + * A get-function to obtain the information in the current Expense List. + * + * @return return the expense ArrayList + */ + public static ArrayList getExpenses() { + return expenses; + } + + public static Expense getExpenseByIndex(int index) { + if (index > numberOfExpenses) { + return null; + } + return expenses.get(index-1); + } + + /** + * Resets the state of the ExpenseManager by clearing all expenses and + * setting the total number of expenses to zero. + *

+ * This method is used for unit testing, ensuring that each test + * starts with a clean slate and does not retain any state from + * previous tests. + *

+ */ + public static void reset() { + numberOfExpenses = 0; + expenses.clear(); + } + + /** + * Provides a global point of access to the single instance of the ExpenseManager class. + *

+ * This method ensures that only one instance of ExpenseManager exists throughout the application's lifetime. + * If an instance does not exist, it creates a new one. Otherwise, it returns the existing instance. + * This method is used for unit testing for Storage to ensure that stored values are correct. + *

+ * + * @return The single instance of the ExpenseManager class. + */ + public static ExpenseManager getInstance() { + if (instance == null) { + instance = new ExpenseManager(); + } + return instance; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/income/Income.java b/src/main/java/seedu/budgetbuddy/transaction/income/Income.java new file mode 100644 index 0000000000..92b946fab1 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/income/Income.java @@ -0,0 +1,49 @@ +package seedu.budgetbuddy.transaction.income; + +import seedu.budgetbuddy.transaction.Transaction; + +import java.time.LocalDate; + +/** + * Represents an income transaction with a description, amount, and date. + * This class extends the Transaction class to manage income-specific details. + */ +public class Income extends Transaction { + /** + * Creates an Income object with the specified description, amount, and date. + * + * @param description The description of the income. + * @param amount The amount of income. + * @param date The date of the income transaction. + */ + public Income(String description, double amount, LocalDate date) { + super(description, amount, date); + } + + /** + * Retrieves the description of the transaction. + * + * @return A string representing the description of the transaction. + */ + public String getDescription() { + return description; + } + + /** + * Retrieves the amount involved in the transaction. + * + * @return A double representing the amount of the transaction. + */ + public double getAmount() { + return amount; + } + + /** + * Retrieves the date of the transaction. + * + * @return A LocalDate representing the date when the transaction occurred. + */ + public LocalDate getDate() { + return date; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/income/IncomeManager.java b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeManager.java new file mode 100644 index 0000000000..46056c998d --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeManager.java @@ -0,0 +1,227 @@ +package seedu.budgetbuddy.transaction.income; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manages a collection of income transactions. + * Provides functionality to add, delete, and list incomes. + */ +public class IncomeManager { + private static final Logger LOGGER = LoggerSetup.getLogger(); + private static int numberOfIncomes = 0; + private static ArrayList incomes = new ArrayList<>(); + private static IncomeManager instance; + + /** + * Construct a IncomeManager of array content incomes + * + * @param incomes is the content to be instantiated + */ + public IncomeManager(ArrayList incomes, int numberOfIncomes) { + assert numberOfIncomes >= 0 : "numberOfIncomes should be greater than 0"; + IncomeManager.incomes = incomes; + IncomeManager.numberOfIncomes = numberOfIncomes; + } + + /** + * Construct a IncomeManager of array content incomes + */ + public IncomeManager() { + + } + + /** + * Adds a new income to the manager. + * + * @param income The income to be added. + */ + public static void addIncome(Income income) { + incomes.add(income); + numberOfIncomes++; + String result = "The following income transaction has been added:\n" + + income + '\n' + + "You have " + numberOfIncomes + " income transaction(s) in total."; + Ui.displayToUser(result); + } + + /** + * Load a new income from storage to the manager. + * + * @param income The income to be added. + */ + public static void loadIncome(Income income) { + incomes.add(income); + numberOfIncomes++; + } + + /** + * Deletes an income from the manager at the specified index. + * + * @param index The index of the income to be deleted. + */ + public static void deleteIncome(int index) { + numberOfIncomes--; + String result = "The following income transaction has been deleted:\n" + + incomes.get(index) + '\n' + + "You have " + numberOfIncomes + " income transaction(s) in total."; + incomes.remove(index); + Ui.displayToUser(result); + } + + /** + * Returns the current number of incomes. + * + * @return The total number of incomes. + */ + public static int getNumberOfIncomes() { + return numberOfIncomes; + } + + /** + * Calculates the total income for a specified month. + * + * @param month The month to calculate income for. + * @return The total income for the month; returns 0.0 if no income is found. + */ + public static double getMonthlyIncome(YearMonth month) { + double sum = 0; + for (Income income : incomes) { + if(month.equals(getYearMonthFromDate(income.getDate()))) { + sum += income.getAmount(); + } + } + return sum; + } + + /** + * Lists all the incomes managed by the manager. + * Displays each income with its corresponding number. + */ + public static void listIncomes() { + if (numberOfIncomes == 0) { + Ui.displayToUser("There are currently no income entries. Try again after adding an income entry."); + return; + } + String result = ""; + int counter = 0; + double sumOfIncome = 0; + for (Income income : incomes) { + counter++; + result += counter + ". " + income.toString() + "\n"; + sumOfIncome += income.getAmount(); + } + result += "There are " + counter + " income(s) in total" + + ", with a sum of $" + String.format("%.2f", sumOfIncome) + "."; + LOGGER.log(Level.INFO, "Listing {0} incomes", numberOfIncomes); + Ui.displayToUser(result); + } + + /** + * List all income that matches with month field that are managed by the manager. + * Displays each income with its corresponding number. + * @param month + */ + public static void listIncomeWithMonth(YearMonth month) { + String result = ""; + int counter = 0; + double filteredIncomeSum = 0; + String monthInString; + + for (Income income : incomes) { + if (month.equals(getYearMonthFromDate(income.getDate()))) { + counter++; + result += counter + ". " + income.toString() + "\n"; + filteredIncomeSum += income.getAmount(); + } + } + if (result.equals("")) { + result = getEmptyDisplayMessage(); + Ui.displayToUser(result); + return; + } + monthInString = month.format(DateTimeFormatter.ofPattern("MMMM yyyy")); + result += "There are " + counter + " income(s) in total for " + monthInString + + ", with a sum of $" + filteredIncomeSum + "."; + LOGGER.log(Level.INFO, "Listing {0} incomes", numberOfIncomes); + Ui.displayToUser(result); + } + + /** + * Gets an Income object based on user input index. + * Searches IncomeList for Income object with desired index and returns reference to object. + * + * @param index user input index to be extracted from IncomeList + * @return an income object for future reference + */ + public static Income getIncomeByIndex(int index) { + if (index > numberOfIncomes) { + return null; + } + return incomes.get(index-1); + } + + /** + * Extract YearMonth value from date + * @param date + * @return + */ + public static YearMonth getYearMonthFromDate(LocalDate date) { + return YearMonth.from(date); + } + + /** + * Generates a custom empty Display income message + * @return custom display income message + */ + public static String getEmptyDisplayMessage() { + return "No income entry with given month found, try again with a different month."; + } + + /** + * A get-function to obtain the information in the current Income List. + * + * @return return the income ArrayList + */ + public static ArrayList getIncomes() { + return incomes; + } + + /** + * Resets the state of the IncomeManager by clearing all incomes and + * setting the total number of incomes to zero. + *

+ * This method is used for unit testing, ensuring that each test + * starts with a clean slate and does not retain any state from + * previous tests. + *

+ */ + public static void reset() { + numberOfIncomes = 0; + incomes.clear(); + } + + /** + * Provides a global point of access to the single instance of the IncomeManager class. + *

+ * This method ensures that only one instance of IncomeManager exists throughout the application's lifetime. + * If an instance does not exist, it creates a new one. Otherwise, it returns the existing instance. + * This method is used for unit testing for Storage to ensure that stored values are correct. + *

+ * + * @return The single instance of the IncomeManager class. + */ + public static IncomeManager getInstance() { + if (instance == null) { + instance = new IncomeManager(incomes, numberOfIncomes); // Pass static fields to constructor + } + return instance; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/income/IncomeSpent.java b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeSpent.java new file mode 100644 index 0000000000..e7b419a9c2 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/income/IncomeSpent.java @@ -0,0 +1,46 @@ +package seedu.budgetbuddy.transaction.income; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.time.YearMonth; + +/** + * The IncomeSpent class provides functionality to calculate and display + * the percentage of income spent for a specified month. + */ +public class IncomeSpent { + + /** + * Calculates the percentage of income spent for a specified month. + * + * @param month The month for which the spending percentage is calculated. + * @return The percentage of income spent for the month. + */ + public static double calculateSpentPercentage(YearMonth month) { + double monthlyIncome = IncomeManager.getMonthlyIncome(month); + double monthlyExpense = ExpenseManager.getMonthlyExpense(month); + return (monthlyExpense / monthlyIncome) * 100; + } + + /** + * Returns a formatted string representing the percentage of income spent + * for a specified month. + * + * @param month The month to format the spending percentage for. + * @return A string displaying the percentage of income spent for the month. + */ + public static String toString(YearMonth month) { + String formattedPercentage = String.format("%.1f", calculateSpentPercentage(month)); + return String.format("Percentage of income spent for %s: %s%%", month, formattedPercentage); + } + + /** + * Displays the percentage of income spent for a specified month to the user. + * + * @param month The month for which to display the spending percentage. + */ + public static void displaySpentPercentage(YearMonth month) { + Ui.displayToUser(toString(month)); + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/saving/Saving.java b/src/main/java/seedu/budgetbuddy/transaction/saving/Saving.java new file mode 100644 index 0000000000..73d62f13cd --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/saving/Saving.java @@ -0,0 +1,54 @@ +package seedu.budgetbuddy.transaction.saving; + +import java.time.YearMonth; + +/** + * Represents a monthly saving with an amount and a YearMonth attribute. + * Monthly savings is calculated based on income - expense. + */ +public class Saving { + private YearMonth yearMonth; + private double savings; + + /** + * Creates a Saving object with the specified YearMonth and savings. + * @param yearMonth YearMonth of the saving + * @param savings amount saved that month + */ + public Saving(YearMonth yearMonth, double savings){ + this.yearMonth = yearMonth; + this.savings = savings; + } + + /** + * Retrieves YearMonth of the object. + * @return A YearMonth representing the month of the saving + */ + public YearMonth getYearMonth() { + return yearMonth; + } + + /** + * Retrieves the amount of savings for the month. + * @return A double representing the amount saved that month + */ + public double getSavings() { + return savings; + } + + /** + * Adds an earned income specified by the user to the savings of that month. + * @param income Earned income to be added. + */ + public void addIncome(double income){ + savings += income; + } + + /** + * Deducts an expense specified by the user from the savings of that month. + * @param expense Expense to be deducted from savings. + */ + public void deductExpense(double expense){ + savings -= expense; + } +} diff --git a/src/main/java/seedu/budgetbuddy/transaction/saving/SavingsManager.java b/src/main/java/seedu/budgetbuddy/transaction/saving/SavingsManager.java new file mode 100644 index 0000000000..371220695b --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/transaction/saving/SavingsManager.java @@ -0,0 +1,113 @@ +package seedu.budgetbuddy.transaction.saving; + +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.logging.Logger; + +/** + * Displays the savings of the user. + * The user can specify if they wish to see their total savings by month or in total. + */ +public class SavingsManager { + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Displays the total savings of the user based on all their incomes and expenses. + * Total Savings = Total Income - Total Expenses + */ + public static String displayTotalSavings(){ + double savings = 0; + double totalIncome = 0; + double totalExpense = 0; + + String result = ""; + if (IncomeManager.getIncomes().size() > 0){ + for (Income income: IncomeManager.getIncomes()){ + totalIncome += income.getAmount(); + } + } + + if (ExpenseManager.getExpenses().size() > 0){ + for (Expense expense: ExpenseManager.getExpenses()){ + totalExpense += expense.getAmount(); + } + } + savings = totalIncome - totalExpense; + + result += "Total Savings: " + String.format("%.2f", savings); + result += "\n" + "Total Income: " + String.format("%.2f", totalIncome); + result += "\n" + "Total Expense: " + String.format("%.2f", totalExpense); + + LOGGER.info("Listing total savings"); + return result; + } + + /** + * Displays the savings by month in chronological order based on their monthly expenses and incomes. + * Monthly savings = Monthly Income - Monthly Expense + */ + public static String displayTotalSavingsByMonth(){ + String result = ""; + ArrayList listYearMonths = new ArrayList<>(); + ArrayList savings = new ArrayList<>(); + if (IncomeManager.getIncomes().size() <= 0 && ExpenseManager.getExpenses().size() <= 0){ + result = "Total savings: 0.00"; + return result; + } + + for (Income income: IncomeManager.getIncomes()){ + YearMonth incomeYearMonth = IncomeManager.getYearMonthFromDate(income.getDate()); + int indexOfSaving = findYearMonthFromArray(listYearMonths, incomeYearMonth); + if (indexOfSaving == -1){ + listYearMonths.add(incomeYearMonth); + savings.add(new Saving(incomeYearMonth, income.getAmount())); + } else{ + savings.get(indexOfSaving).addIncome(income.getAmount()); + } + } + + + for (Expense expense : ExpenseManager.getExpenses()) { + YearMonth expenseYearMonth = ExpenseManager.getYearMonthFromDate(expense.getDate()); + int indexOfSaving = findYearMonthFromArray(listYearMonths, expenseYearMonth); + if (indexOfSaving == -1) { + listYearMonths.add(expenseYearMonth); + savings.add(new Saving(expenseYearMonth, -expense.getAmount())); + } else { + savings.get(indexOfSaving).deductExpense(expense.getAmount()); + } + } + + + savings.sort(Comparator.comparing(Saving::getYearMonth)); + for (Saving saving: savings){ + result += "Savings in " + saving.getYearMonth() +": " + String.format("%.2f", saving.getSavings()); + if (saving != savings.get(savings.size()-1)){ + result += "\n"; + } + } + LOGGER.info("Displaying savings by month"); + return result; + } + + /** + * Helper method to get the index of a specified YearMonth in the ArrayList to update savings. + * @param listYearMonth ArrayList containing all YearMonths of Saving objects. + * @param yearMonth Specific YearMonth to find in the ArrayList + * @return index of the YearMonth in the ArrayList, otherwise -1. + */ + private static int findYearMonthFromArray(ArrayList listYearMonth, YearMonth yearMonth){ + if (!listYearMonth.contains(yearMonth)){ + return -1; + } else{ + return listYearMonth.indexOf(yearMonth); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/util/LoggerSetup.java b/src/main/java/seedu/budgetbuddy/util/LoggerSetup.java new file mode 100644 index 0000000000..0e3522acd7 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/util/LoggerSetup.java @@ -0,0 +1,35 @@ +package seedu.budgetbuddy.util; + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +public class LoggerSetup { + private static final Logger logger = Logger.getLogger(LoggerSetup.class.getName()); + + static { + try { + //Ensure the Log Directory Exist + File logDirectory = new File("logs"); + if (!logDirectory.exists()) { + logDirectory.mkdir(); + } + + // Create a file handler that writes logs messages to logs/app.log + FileHandler fileHandler = new FileHandler("logs/app.log", true); + fileHandler.setFormatter(new SimpleFormatter()); // Use a simple text format for the logs + + logger.addHandler(fileHandler); + logger.setUseParentHandlers(false); + + } catch (IOException e) { + logger.severe("Failed to initialize log file handler: " + e.getMessage()); + } + } + + public static Logger getLogger() { + return logger; + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/AmountValidator.java b/src/main/java/seedu/budgetbuddy/validators/AmountValidator.java new file mode 100644 index 0000000000..a0c036ac33 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/AmountValidator.java @@ -0,0 +1,29 @@ +package seedu.budgetbuddy.validators; + +/** + * Validates and converts amount from String to double. + */ +public class AmountValidator { + /** + * Converts amount from String to double. + * + * @param part The String containing the amount. + * @return The parsed amount or -1 if invalid. + */ + public static double validateAmount(String part) { + for (int i = 2; i < part.length(); i++) { + if (!(Character.isDigit(part.charAt(i)) || part.charAt(i) == '.'|| part.charAt(i) == '-')) { + return -1; + } + } + try { + double amount = Double.parseDouble(part.substring(2)); + if (amount * 100 % 1 != 0) { + return -1; + } + return amount; + } catch (NumberFormatException e) { + return -1; // Indicates invalid amount + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/CategoryValidator.java b/src/main/java/seedu/budgetbuddy/validators/CategoryValidator.java new file mode 100644 index 0000000000..2461c0da4e --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/CategoryValidator.java @@ -0,0 +1,40 @@ +package seedu.budgetbuddy.validators; + +import seedu.budgetbuddy.transaction.Category; + +/** + * Validates category input for transactions. + */ +public class CategoryValidator { + + /** + * Parses the category from the command part. + * + * @param part The command part containing the category. + * @return The parsed category or OTHERS if invalid. + */ + public static Category validateCategory(String part) { + String categoryStr = part.substring(2).toUpperCase(); + try { + return Category.valueOf(categoryStr); + } catch (IllegalArgumentException e) { + return Category.OTHERS; // Default category if invalid + } + } + + /** + * Parses the category from user inputs. Used for any interaction with data extraction from existing list + * (example: edit operations) + * + * @param part The command part containing the category. + * @return The parsed category or OTHERS if invalid. + */ + public static Category validateSearchCategory(String part) { + String categoryStr = part.substring(2).toUpperCase(); + try { + return Category.valueOf(categoryStr); + } catch (IllegalArgumentException e) { + return null; // Default category if invalid + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/DateValidator.java b/src/main/java/seedu/budgetbuddy/validators/DateValidator.java new file mode 100644 index 0000000000..d8090d4214 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/DateValidator.java @@ -0,0 +1,41 @@ +package seedu.budgetbuddy.validators; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + +/** + * Validates and converts the date given. + */ +public class DateValidator { + /** + * Validates the date given. + * + * @param part The part containing the date. + * @return The parsed date or null if invalid. + */ + public static LocalDate validateDate(String part) { + try { + return LocalDate.parse(part.substring(2), + DateTimeFormatter.ofPattern("d/M/uuuu").withResolverStyle(ResolverStyle.STRICT)); + } catch (DateTimeParseException e) { + return null; // Indicates invalid date + } + } + + /** + * Parses the date from the command part. + * + * @param part The command part containing the date. + * @return The parsed YearMonth or null if invalid. + */ + public static YearMonth validateYearMonth(String part) { + try { + return YearMonth.parse(part.substring(2), DateTimeFormatter.ofPattern("M/yyyy")); + } catch (DateTimeParseException e) { + return null; // Indicates invalid date + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/IndexValidator.java b/src/main/java/seedu/budgetbuddy/validators/IndexValidator.java new file mode 100644 index 0000000000..912f33bae5 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/IndexValidator.java @@ -0,0 +1,47 @@ +package seedu.budgetbuddy.validators; + +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +/** + * Validates the index input. + */ +public class IndexValidator { + /** + * Validates the given expense index. + * + * @param value The input string representing the expense index. + * @return The validated index if it is a valid number within the range, or -1 if invalid. + */ + public static int validateExpenseIndex(String value) { + int index; + try { + index = Integer.parseInt(value); + } catch (NumberFormatException e) { + return -1; + } + if (index >= 1 && index <= ExpenseManager.getNumberOfExpenses()) { + return index; + } + return -1; + } + + /** + * Validates the given income index. + * + * @param value The input string representing the income index. + * @return The validated index if it is a valid number within the range, or -1 if invalid. + */ + public static int validateIncomeIndex(String value) { + int index; + try { + index = Integer.parseInt(value); + } catch (NumberFormatException e) { + return -1; + } + if (index >= 1 && index <= IncomeManager.getNumberOfIncomes()) { + return index; + } + return -1; + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/budget/AddBudgetValidator.java b/src/main/java/seedu/budgetbuddy/validators/budget/AddBudgetValidator.java new file mode 100644 index 0000000000..52a10d7b4c --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/budget/AddBudgetValidator.java @@ -0,0 +1,67 @@ +package seedu.budgetbuddy.validators.budget; + +import seedu.budgetbuddy.commands.budget.AddBudgetCommand; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Logger; + +import static seedu.budgetbuddy.validators.AmountValidator.validateAmount; +import static seedu.budgetbuddy.validators.CategoryValidator.validateCategory; +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +/** + * Validates commands for adding budgets. + */ +public class AddBudgetValidator { + private static final Logger LOGGER = LoggerSetup.getLogger(); + + public static Command processCommand(String command) throws BudgetBuddyException { + assert command != null : "Command cannot be null"; + + if (command.equals("add budget")) { + LOGGER.warning("Attempted to add budget without amount."); + throw new BudgetBuddyException("No amount provided."); + } + + String trimmedCommand = command.substring("add budget ".length()); + String[] parts = trimmedCommand.split(" "); + + // Initialize default values + double amount = 0; // invalid amount initially + YearMonth date = YearMonth.now(); + Category category = Category.OTHERS; + + // Process parts to extract details + for (String part : parts) { + if (part.startsWith("a/")) { + amount = validateAmount(part); + if (amount == -1) { + throw new BudgetBuddyException("Invalid amount format."); + } + } else if (part.startsWith("m/")) { + date = validateYearMonth(part); + } else if (part.startsWith("c/")) { + category = validateCategory(part); + } else { + throw new BudgetBuddyException("Unrecognised input: '" + part + "'. Please check the command format."); + } + } + + // Validate amount + if (amount <= 0) { + throw new BudgetBuddyException("Invalid amount: " + amount + ". Amount must be a positive value."); + } + + // Validate date + if (date == null) { + throw new BudgetBuddyException("Invalid date format. Use m/MM/yyyy."); + } + + // All validations passed, return the command + return new AddBudgetCommand(amount, date, category); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidator.java b/src/main/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidator.java new file mode 100644 index 0000000000..5a79ea5c24 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidator.java @@ -0,0 +1,78 @@ +package seedu.budgetbuddy.validators.budget; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.budget.DeductBudgetCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Logger; + +import static seedu.budgetbuddy.validators.AmountValidator.validateAmount; +import static seedu.budgetbuddy.validators.CategoryValidator.validateCategory; +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +/** + * Validates commands for deducting budgets. + */ +public class DeductBudgetValidator { + private static final Logger LOGGER = LoggerSetup.getLogger(); + + public static Command processCommand(String command) throws BudgetBuddyException { + assert command != null : "Command cannot be null"; + + if (command.equals("deduct budget")) { + LOGGER.warning("Attempted to deduct budget without amount."); + throw new BudgetBuddyException("No amount provided."); + } + + String trimmedCommand = command.substring("deduct budget ".length()); + String[] parts = trimmedCommand.split(" "); + + // Initialize default values + double amount = 0; // invalid amount initially + YearMonth date = YearMonth.now(); + Category category = Category.OTHERS; + + // Process parts to extract details + for (String part : parts) { + if (part.startsWith("a/")) { + amount = validateAmount(part); + if (amount == -1) { + throw new BudgetBuddyException("Invalid amount format."); + } + } else if (part.startsWith("m/")) { + date = validateYearMonth(part); + } else if (part.startsWith("c/")) { + category = validateCategory(part); + } else { + throw new BudgetBuddyException("Unrecognised input: '" + part + "'. Please check the command format."); + } + } + + // Validate amount + if (amount <= 0) { + throw new BudgetBuddyException("Invalid amount: " + amount + ". Must be a positive value."); + } + + // Validate date + if (date == null) { + throw new BudgetBuddyException("Invalid date format. Use m/MM/yyyy."); + } + + // Check if budget exists for the date + if (BudgetManager.getBudget(date) == null) { + throw new BudgetBuddyException("Budget does not exist for the specified date: " + date); + } + + if (BudgetManager.getBudget(date).getCategoryBudgetAmount(category) <= 0) { + throw new BudgetBuddyException("Budget's category does not exist for the specified date: " + category + + ", " + date + "\n" + "Try deducting from another category."); + } + + // All validations passed, return the command + return new DeductBudgetCommand(amount, date, category); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/budget/ListBudgetValidator.java b/src/main/java/seedu/budgetbuddy/validators/budget/ListBudgetValidator.java new file mode 100644 index 0000000000..8f007ed835 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/budget/ListBudgetValidator.java @@ -0,0 +1,41 @@ +package seedu.budgetbuddy.validators.budget; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.budget.ListBudgetCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; + +import java.time.YearMonth; + +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +/** + * Validates user commands for listing budgets. + */ +public class ListBudgetValidator { + + /** + * Processes the command string to determine if it is valid for listing budgets. + * If valid, it returns a ListBudgetCommand with the parsed date. + * + * @param command The command string entered by the user. + * @return A ListBudgetCommand if valid; otherwise, an IncorrectCommand. + */ + public static Command processCommand(String command) throws BudgetBuddyException { + if (command.equals("list budgets")) { + return new ListBudgetCommand(null); // No date provided, list all budgets + } + + // Check for date in the command + String trimmedCommand = command.substring("list budgets ".length()).trim(); + YearMonth date = null; + + if (!trimmedCommand.isEmpty()) { + date = validateYearMonth(trimmedCommand); + if (date == null) { + throw new BudgetBuddyException("Invalid format. Use 'list budgets [m/MM/yyyy]'."); + } + } + + return new ListBudgetCommand(date); // Return command with date + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/AddExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/AddExpenseValidator.java new file mode 100644 index 0000000000..6ec8eaacd1 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/AddExpenseValidator.java @@ -0,0 +1,82 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.commands.expense.AddExpenseCommand; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.transaction.Category; + +import java.time.LocalDate; + +import static seedu.budgetbuddy.validators.AmountValidator.validateAmount; +import static seedu.budgetbuddy.validators.CategoryValidator.validateCategory; +import static seedu.budgetbuddy.validators.DateValidator.validateDate; + +public class AddExpenseValidator { + + /** + * Processes the given command to create a new expense entry. + * + * @param command The command string to be processed. + * @return A Command object representing the add expense command + * or an IncorrectCommand if validation fails. + */ + public static Command processCommand(String command) { + if (command.equals("add expense")) { + return new IncorrectCommand("No description provided."); + } + + String trimmedCommand = command.substring("add expense ".length()); + String[] parts = trimmedCommand.split(" "); + + // Initialize default values + String description = ""; + double amount = 0; // invalid amount initially + LocalDate date = LocalDate.now(); + Category category = Category.OTHERS; + boolean hasAmount = false; + + // Process parts to extract details + for (String part : parts) { + String prefix = part.length() >= 2 ? part.substring(0, 2) : ""; + switch (prefix) { + + case "a/": + amount = validateAmount(part); + hasAmount = true; + if (amount <= 0) { + return new IncorrectCommand("Amount should be a positive number with up to 2 " + + "decimal places."); + } + break; + + case "d/": + date = validateDate(part); + if (date == null) { + return new IncorrectCommand("Invalid date format. Use d/DD/MM/YYYY."); + } + break; + + case "c/": + category = validateCategory(part); + break; + + default: + description += part + " "; + } + } + + description = description.trim(); + // Validate description + if (description.isEmpty()) { + return new IncorrectCommand("Description cannot be empty."); + } + + // Validate amount + if (!hasAmount) { + return new IncorrectCommand("Amount not entered."); + } + + // All validations passed, return the command + return new AddExpenseCommand(description, amount, date, category); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidator.java new file mode 100644 index 0000000000..2726acab26 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidator.java @@ -0,0 +1,36 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.expense.DeleteExpenseCommand; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.validators.IndexValidator; + +/** + * Validates the command for deleting an expense. + */ +public class DeleteExpenseValidator { + + /** + * Processes the delete expense command and returns the appropriate command object. + * + * @param command The command string entered by the user. + * @return A Command object representing the delete expense action or an IncorrectCommand if the index is invalid. + */ + public static Command processCommand(String command) { + String trimmedCommand; + + try { + trimmedCommand = command.substring("delete expense ".length()); + } catch (IndexOutOfBoundsException e) { + return new IncorrectCommand("Index not given"); + } + + int index = IndexValidator.validateExpenseIndex(trimmedCommand); + + if (index == -1) { + return new IncorrectCommand("Invalid Index"); + } + + return new DeleteExpenseCommand(index - 1); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidator.java new file mode 100644 index 0000000000..ebbd7599e9 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidator.java @@ -0,0 +1,49 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.DisplayExpensesForMonthWithCategoriesGraphCommand; +import seedu.budgetbuddy.util.LoggerSetup; +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +import java.time.YearMonth; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Validates and processes commands related to displaying expenses for a specific month with categories. + */ +public class DisplayExpensesForMonthWithCategoriesValidator { + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Processes the given command to create a command object for displaying expenses by month and category. + * + * @param command The command string input by the user. + * @return A Command object corresponding to the user's input, or an IncorrectCommand if the input is invalid. + */ + public static Command processCommand(String command){ + LOGGER.log(Level.INFO, "Processing command: " + command); + YearMonth yearMonth; + + String trimmedCommand = command.substring("display expenses with categories".length()).trim(); + if (trimmedCommand.isEmpty()) { + LOGGER.log(Level.WARNING, "Trimmed command is empty"); + return new IncorrectCommand("Please provide a valid month"); + } + + if (trimmedCommand.startsWith("m/")) { + yearMonth = validateYearMonth(trimmedCommand); + } else { + LOGGER.log(Level.WARNING, "Invalid Command: " + trimmedCommand); + return new IncorrectCommand("Unknown command: " + trimmedCommand + " Please use m/MM/YYYY"); + } + + if (yearMonth == null) { + LOGGER.log(Level.WARNING, "Invalid YearMonth: " + yearMonth); + return new IncorrectCommand("Invalid Date"); + } else { + return new DisplayExpensesForMonthWithCategoriesGraphCommand(yearMonth); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidator.java new file mode 100644 index 0000000000..3a3849369a --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidator.java @@ -0,0 +1,71 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.DisplayTotalExpensesCommand; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A validator class for processing and validating the "display yearly expenses" command. + * It extracts and validates year information from the command. + */ +public class DisplayTotalExpensesValidator { + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Processes the command input, validates the year, and returns a corresponding command. + * + * @param command The raw command string entered by the user. + * @return A {@link Command} object, either {@link DisplayTotalExpensesCommand} or {@link IncorrectCommand}. + */ + public static Command processCommand(String command) { + LOGGER.log(Level.INFO, "Processing command: " + command); + + // Extract the portion of the command after "display yearly expenses" + String trimmedCommand = command.substring("display monthly expenses".length()).trim(); + if (trimmedCommand.isEmpty()) { + LOGGER.log(Level.WARNING, "Trimmed command is empty"); + return new IncorrectCommand("Please provide a year."); + } + + // Check if the command starts with "y/" and extract the year argument + String yearArg; + if (trimmedCommand.startsWith("y/")) { + yearArg = trimmedCommand.substring(2); // Remove the "y/" prefix + } else { + LOGGER.log(Level.WARNING, "Invalid Command: " + trimmedCommand); + return new IncorrectCommand("Unknown command '" + trimmedCommand + "'. " + + "Expected format: 'y/YYYY'"); + } + + // Validate and parse the year + if (!isValidYear(yearArg)) { + LOGGER.log(Level.WARNING, "Invalid year: " + yearArg); + return new IncorrectCommand("Invalid or missing year format."); + } + + int year = Integer.parseInt(yearArg); + return new DisplayTotalExpensesCommand(year); + } + + /** + * Validates the year input to ensure it consists of four digits and falls within the valid range (1900 to 2100). + * + * @param year The year string to validate. + * @return True if the year is valid, false otherwise. + */ + private static boolean isValidYear(String year) { + if (year.length() != 4) { + return false; + } + try { + Integer.parseInt(year); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/EditExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/EditExpenseValidator.java new file mode 100644 index 0000000000..560733bd95 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/EditExpenseValidator.java @@ -0,0 +1,108 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.EditExpenseCommand; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.LocalDate; +import java.util.logging.Logger; + +import static seedu.budgetbuddy.validators.AmountValidator.validateAmount; +import static seedu.budgetbuddy.validators.CategoryValidator.validateSearchCategory; +import static seedu.budgetbuddy.validators.DateValidator.validateDate; + +public class EditExpenseValidator { + + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Processes the command string to determine if it is valid expense index. + * If valid, it creates an EditExpenseCommand object which will be used for subsequent execution. + * otherwise, it returns an IncorrectCommand object with the error. + * + * @param command first input given by user + * @return command object + */ + public static Command processFirstCommand(String command) { + if (command.equals("edit expense")) { + return new IncorrectCommand("No index detected, try again with an index."); + } + if (command.startsWith("edit expenses")) { + return new IncorrectCommand("Invalid input"); + } + try { + String trimmedCommand = command.substring("edit expense ".length()); + String[] parts = trimmedCommand.split(" "); + int editIndex = Integer.parseInt(parts[0]); + if (editIndex < 0) { + return new IncorrectCommand("Edit index must be greater than 0."); + } + Expense expense = ExpenseManager.getExpenseByIndex(editIndex); + if (expense == null) { + return new IncorrectCommand("Input index is larger than the number of expenses. " + + "Try with a smaller index"); + } else { + return new EditExpenseCommand(expense); + } + } catch (NumberFormatException e) { + return new IncorrectCommand("Index must be a valid number larger than 0."); + } + + } + + /** + * Processes the command string to determine if it is valid for editing. + * If valid, it updates the fields to be changed and returns a true for validity of command. + * otherwise, it will display error message and return a false validity of command. + * For any invalid category input, it will be set to others by default. + * + * @param command Input given by user + * @return Validity of command {@code Boolean} + */ + public static boolean processSecondCommand(String command){ + + String[] parts = command.split(" "); + + //Process Initial Value + LocalDate date = null; + Category category = null; + double amount = -1.0; + + //Process parts to extract details + for (String part : parts) { + if (part.startsWith("d/")) { + date = validateDate(part); + if (date == null) { + LOGGER.warning("Invalid date format. Date found: " + part); + Ui.displayToUser("Invalid month format. Use d/dd/mm/yyyy."); + return false; + } + } else if (part.startsWith("c/")) { + category = validateSearchCategory(part); + if (category == null) { + LOGGER.warning("Invalid category format. Category found: " + part); + Ui.displayToUser("Unknown category detected. Try again with a valid category."); + return false; + } + } else if (part.startsWith("a/")) { + amount = validateAmount(part); + if (amount <= 0.0) { + Ui.displayToUser("Amount should be a positive number with up to 2 decimal places."); + return false; + } + } + } + + //set Fields to be changed + EditExpenseCommand.setDate(date); + EditExpenseCommand.setCategory(category); + EditExpenseCommand.setAmount(amount); + + return true; + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/ListExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/ListExpenseValidator.java new file mode 100644 index 0000000000..189ddacfa4 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/ListExpenseValidator.java @@ -0,0 +1,78 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.ListExpenseCommand; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.YearMonth; +import java.util.logging.Logger; + +import static seedu.budgetbuddy.validators.CategoryValidator.validateSearchCategory; +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +/** + * @author Kenneth + */ +public class ListExpenseValidator{ + + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Processes the command string to determine if it is valid for displaying expenses. + * If valid, it returns a DisplayExpenseCommand with the parsed date. + * + * @param command + * @return + */ + public static Command processCommand(String command) { + if (command.equals("list expenses")){ + return new ListExpenseCommand(); + } + + String trimmedCommand = command.substring("list expenses ".length()); + String[] parts = trimmedCommand.split(" "); + + //Process Initial Value + YearMonth month = null; + Category category = null; + + //Process parts to extract details + for (String part : parts) { + if (part.startsWith("m/")) { + month = validateYearMonth(part); + if (month == null) { + LOGGER.warning("Invalid month format. Month found: " + part); + return new IncorrectCommand("Invalid month format. Use m/MM/yyyy."); + } + } else if (part.startsWith("c/")) { + category = validateSearchCategory(part); + if (category == null) { + LOGGER.warning("Invalid Category. Category found: " + part); + return new IncorrectCommand("Unknown category. Use a valid category."); + } + } + } + + //Check of Display Type + return checkListType(category, month); + } + + /** + * Checks the value of category and date and returns the corresponding + * DisplayExpenseCommand type + * @param category + * @param date + * @return + */ + public static Command checkListType(Category category, YearMonth date) { + if (category != null && date == null) { + return new ListExpenseCommand(category); + } else if (category == null && date != null) { + return new ListExpenseCommand(date); + } else { + return new ListExpenseCommand(category, date); + } + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/expense/SearchExpenseValidator.java b/src/main/java/seedu/budgetbuddy/validators/expense/SearchExpenseValidator.java new file mode 100644 index 0000000000..269c067bcc --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/expense/SearchExpenseValidator.java @@ -0,0 +1,20 @@ +package seedu.budgetbuddy.validators.expense; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.expense.SearchExpenseCommand; + +public class SearchExpenseValidator { + /** + * Validates command input by user to see if string input has a keyword provided as a descriptor + * @param command Command input by user. + * @return new SearchExpenseCommand object. + */ + public static Command processCommand(String command){ + assert command != null: "Command cannot be null"; + if (command.equals("search expenses")){ + return new SearchExpenseCommand(); + } + String trimmedCommand = command.substring("search expenses ".length()); + return new SearchExpenseCommand(trimmedCommand); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/income/AddIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/AddIncomeValidator.java new file mode 100644 index 0000000000..cbfed24170 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/income/AddIncomeValidator.java @@ -0,0 +1,80 @@ +package seedu.budgetbuddy.validators.income; + +import seedu.budgetbuddy.commands.income.AddIncomeCommand; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; + +import java.time.LocalDate; + +import static seedu.budgetbuddy.validators.AmountValidator.validateAmount; +import static seedu.budgetbuddy.validators.DateValidator.validateDate; + + +/** + * Validates the command for adding an income. + */ +public class AddIncomeValidator { + + /** + * Processes the given command to create a new income entry. + * + * @param command The command string to be processed. + * @return A Command object representing the add income command + * or an IncorrectCommand if validation fails. + */ + public static Command processCommand(String command) { + if (command.equals("add income")) { + return new IncorrectCommand("No description provided."); + } + + String trimmedCommand = command.substring("add income ".length()); + String[] parts = trimmedCommand.split(" "); + + // Initialize default values + String description = ""; + double amount = 0; // invalid amount initially + LocalDate date = LocalDate.now(); + boolean hasAmount = false; + + // Process parts to extract details + for (String part : parts) { + String prefix = part.length() >= 2 ? part.substring(0, 2) : ""; + switch (prefix) { + + case "a/": + amount = validateAmount(part); + hasAmount = true; + if (amount <= 0) { + return new IncorrectCommand("Amount should be a positive number with up to 2 " + + "decimal places."); + } + break; + + case "d/": + date = validateDate(part); + if (date == null) { + return new IncorrectCommand("Invalid date format. Use d/dd/MM/yyyy."); + } + break; + + default: + description += part + " "; + } + } + + description = description.trim(); + + // Validate description + if (description.isEmpty()) { + return new IncorrectCommand("Description cannot be empty."); + } + + // Validate amount + if (hasAmount == false) { + return new IncorrectCommand("Amount not entered."); + } + + // All validations passed, return the command + return new AddIncomeCommand(description, amount, date); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidator.java new file mode 100644 index 0000000000..6b3496b31f --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidator.java @@ -0,0 +1,36 @@ +package seedu.budgetbuddy.validators.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.income.DeleteIncomeCommand; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.validators.IndexValidator; + +/** + * Validates the command for deleting an income. + */ +public class DeleteIncomeValidator { + + /** + * Processes the delete income command and returns the appropriate command object. + * + * @param command The command string entered by the user. + * @return A Command object representing the delete income action or an IncorrectCommand if the index is invalid. + */ + public static Command processCommand(String command) { + String trimmedCommand; + + try { + trimmedCommand = command.substring("delete income ".length()); + } catch (IndexOutOfBoundsException e) { + return new IncorrectCommand("Index not given"); + } + + int index = IndexValidator.validateIncomeIndex(trimmedCommand); + + if (index == -1) { + return new IncorrectCommand("Invalid Index"); + } + + return new DeleteIncomeCommand(index - 1); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidator.java new file mode 100644 index 0000000000..90abc81789 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidator.java @@ -0,0 +1,54 @@ +package seedu.budgetbuddy.validators.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.income.DisplayIncomeSpentCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.time.LocalDate; +import java.time.YearMonth; + +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +/** + * Validates and processes the command to display the percentage of income spent for a specified month. + */ +public class DisplayIncomeSpentValidator { + + /** + * Processes the given command and returns a corresponding Command object. + * + * @param command The command string to process. + * @return Command representing the display income spent command for the specified month. + * @throws BudgetBuddyException If no income is recorded for the specified month. + */ + public static Command processCommand(String command) throws BudgetBuddyException { + YearMonth month = null; + + if (command.equals("display income spent")) { + month = YearMonth.from(LocalDate.now()); + } else { + String trimmedCommand = command.substring("display income spent ".length()); + String[] parts = trimmedCommand.split(" "); + + //Process parts to extract details + for (String part : parts) { + if (part.startsWith("m/")) { + month = validateYearMonth(part); + if (month == null) { + throw new BudgetBuddyException("Invalid month format. Use m/MM/yyyy."); + } + } else { + throw new BudgetBuddyException("Unrecognised input: '" + part + "'. " + + "Please check the command format."); + } + } + } + + if (IncomeManager.getMonthlyIncome(month) <= 0) { + throw new BudgetBuddyException("No income recorded for the month: " + month); + } + + return new DisplayIncomeSpentCommand(month); + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/income/EditIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/EditIncomeValidator.java new file mode 100644 index 0000000000..eb65c35865 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/income/EditIncomeValidator.java @@ -0,0 +1,101 @@ +package seedu.budgetbuddy.validators.income; + +import seedu.budgetbuddy.Ui; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.income.EditIncomeCommand; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.time.LocalDate; +import java.util.logging.Logger; + +import static seedu.budgetbuddy.validators.AmountValidator.validateAmount; +import static seedu.budgetbuddy.validators.DateValidator.validateDate; + +public class EditIncomeValidator { + + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Processes the command string to determine if it is valid income index. + * If valid, it creates an EditIncomeCommand object which will be used for subsequent execution. + * otherwise, it returns an IncorrectCommand object with the error. + * + * @param command first input given by user + * @return command object + */ + public static Command processFirstCommand(String command) { + if (command.equals("edit income")) { + return new IncorrectCommand("No index detected, try again with an index."); + } + if (command.startsWith("edit incomes")) { + return new IncorrectCommand("Invalid input"); + } + try { + String trimmedCommand = command.substring("edit income ".length()); + String[] parts = trimmedCommand.split(" "); + int editIndex = Integer.parseInt(parts[0]); + if (editIndex < 0) { + return new IncorrectCommand("Edit index must be greater than 0."); + } + Income income = IncomeManager.getIncomeByIndex(editIndex); + if (income == null) { + return new IncorrectCommand("Input index is larger than the number of incomes. " + + "Try with a smaller index"); + } else { + return new EditIncomeCommand(income); + } + } catch (NumberFormatException e) { + return new IncorrectCommand("Index must be a valid number larger than 0."); + } + + } + + /** + * Processes the command string to determine if it is valid for editing. + * If valid, it updates the fields to be changed and returns a true for validity of command. + * otherwise, it will display error message and return a false validity of command. + * + * @param command Input given by user + * @return Validity of command {@code Boolean} + */ + public static boolean processSecondCommand(String command) { + + String[] parts = command.split(" "); + + //Process Initial Value + LocalDate date = null; + double amount = -1.0; + + //Process parts to extract details + for (String part : parts) { + if (part.startsWith("d/")) { + date = validateDate(part); + if (date == null) { + LOGGER.warning("Invalid date format. Date found: " + part); + Ui.displayToUser("Invalid month format. Use d/dd/mm/yyyy."); + return false; + } + } else if (part.startsWith("a/")) { + amount = validateAmount(part); + if (amount < 0.0) { + Ui.displayToUser("Invalid amount format. Amount should be a positive number."); + return false; + } + } + } + + if (date == null && amount == -1.0) { + Ui.displayToUser("No valid fields detected, Exiting change menu."); + return false; + } + + //set Fields to be changed + EditIncomeCommand.setDate(date); + EditIncomeCommand.setAmount(amount); + + return true; + } +} diff --git a/src/main/java/seedu/budgetbuddy/validators/income/ListIncomeValidator.java b/src/main/java/seedu/budgetbuddy/validators/income/ListIncomeValidator.java new file mode 100644 index 0000000000..9f879dbc2f --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/income/ListIncomeValidator.java @@ -0,0 +1,44 @@ +package seedu.budgetbuddy.validators.income; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.income.ListIncomeCommand; + +import java.time.YearMonth; + +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +public class ListIncomeValidator { + + /** + * Processes the command string to determine if it is valid for displaying income. + * If valid, it returns a DisplayIncomeCommand with the parsed date. + * + * @param command + * @return + */ + public static Command processCommand(String command) { + if (command.equals("list incomes")){ + return new ListIncomeCommand(); + } + + String trimmedCommand = command.substring("list incomes ".length()); + String[] parts = trimmedCommand.split(" "); + + //Process Initial Value + YearMonth month = null; + + //Process parts to extract details + for (String part : parts) { + if (part.startsWith("m/")) { + month = validateYearMonth(part); + if (month == null) { + return new IncorrectCommand("Invalid month format. Use m/MM/yyyy."); + } + } + } + + return new ListIncomeCommand(month); + } + +} diff --git a/src/main/java/seedu/budgetbuddy/validators/saving/DisplaySavingsValidator.java b/src/main/java/seedu/budgetbuddy/validators/saving/DisplaySavingsValidator.java new file mode 100644 index 0000000000..1463f8b142 --- /dev/null +++ b/src/main/java/seedu/budgetbuddy/validators/saving/DisplaySavingsValidator.java @@ -0,0 +1,32 @@ +package seedu.budgetbuddy.validators.saving; + +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.saving.DisplaySavingsCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.util.LoggerSetup; + +import java.util.logging.Logger; + +/** + * Validator class for validating user input and the "display savings" command. + */ +public class DisplaySavingsValidator { + private static final Logger LOGGER = LoggerSetup.getLogger(); + + /** + * Validates command input by user to see if string input has a keyword provided as a descriptor + * @param command Command input by user. + * @return new DisplaySavingsCommand object. + * @throws BudgetBuddyException if user input is not "display savings" or "display savings m/" + */ + public static Command processCommand(String command) throws BudgetBuddyException{ + assert command != null: "Command cannot be null"; + if (command.equals("display savings m/")){ + return new DisplaySavingsCommand(true); + } else if (command.equals("display savings")){ + return new DisplaySavingsCommand(false); + } + LOGGER.info("Invalid display savings message by user."); + throw new BudgetBuddyException("Key in display savings m/ or display savings"); + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java similarity index 77% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java index 2dda5fd651..fb8deccc50 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/seedu/budgetbuddy/BudgetBuddyTest.java @@ -1,10 +1,10 @@ -package seedu.duke; +package seedu.budgetbuddy; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -class DukeTest { +class BudgetBuddyTest { @Test public void sampleTest() { assertTrue(true); diff --git a/src/test/java/seedu/budgetbuddy/ParserTest.java b/src/test/java/seedu/budgetbuddy/ParserTest.java new file mode 100644 index 0000000000..33c45b1a80 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/ParserTest.java @@ -0,0 +1,101 @@ +package seedu.budgetbuddy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.AddExpenseCommand; +import seedu.budgetbuddy.commands.budget.AddBudgetCommand; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ParserTest { + private Parser parser; + + @BeforeEach + public void setUp() { + ExpenseManager expenseManager = new ExpenseManager(); + IncomeManager incomeManager = new IncomeManager(); + BudgetManager budgetManager = new BudgetManager(); + parser = new Parser(expenseManager, incomeManager, budgetManager); + } + + @Test + public void parseCommand_validAddExpenseCommand_returnsAddExpenseCommand() throws BudgetBuddyException { + String command = "add expense KFC lunch a/10.50 d/28/10/2024 c/FOOD"; + + Command result = parser.parseCommand(command); + + assertInstanceOf(AddExpenseCommand.class, result); + } + + @Test + public void parseCommand_validAddBudgetCommand_returnsAddBudgetCommand() throws BudgetBuddyException { + String command = "add budget a/700 m/11/2024 c/TRANSPORT"; + + Command result = parser.parseCommand(command); + + assertInstanceOf(AddBudgetCommand.class, result); + } + + @Test + public void parseCommand_invalidCommand_returnsIncorrectCommand() throws BudgetBuddyException { + String command = "invalid command"; + + Command result = parser.parseCommand(command); + + assertInstanceOf(IncorrectCommand.class, result); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + assertEquals("Invalid input", incorrectCommand.getFeedbackToUser()); + } + + @Test + public void parseFile_validExpenseLine_parsesExpenseCorrectly() { + // Given a valid expense input line + String input = "expense | Lunch | 15 | 10/10/2024 | OTHERS"; + + // Parse the file input (use the class name since parseFile is static) + Parser.parseFile(input); + + Expense expense= ExpenseManager.getExpenses().get(0); + assertEquals(expense.getAmount(), 15); + assertEquals(expense.getDescription(), "Lunch"); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate expectedDate = LocalDate.parse("10/10/2024", formatter); + assertEquals(expectedDate, expense.getDate()); + + assertEquals(Category.OTHERS, expense.getCategory()); + } + + @Test + public void parseFile_validBudgetLine_parsesBudgetCorrectly() { + // Given a valid budget input line + String input = "budget | Budget for October | 2024-10 | {food=100, entertainment=50}"; + + // Parse the file input (use the class name) + Parser.parseFile(input); + + Budget budget = BudgetManager.getBudgets().get(0); + assertEquals(budget.getCategoryBudgetAmount(Category.FOOD), 100); + assertEquals(budget.getCategoryBudgetAmount(Category.ENTERTAINMENT), 50); + + YearMonth expectedDate = YearMonth.parse("2024-10"); + assertEquals(budget.getDate(), expectedDate); + } +} + + diff --git a/src/test/java/seedu/budgetbuddy/StorageTest.java b/src/test/java/seedu/budgetbuddy/StorageTest.java new file mode 100644 index 0000000000..4adda25bb0 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/StorageTest.java @@ -0,0 +1,134 @@ +package seedu.budgetbuddy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalDate; +import java.time.YearMonth; +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StorageTest { + + private Storage storage; + private File tempFile; + + @BeforeEach + public void setUp() throws IOException { + // Create a temporary file for testing + tempFile = File.createTempFile("testStorage", ".txt"); + tempFile.deleteOnExit(); // Ensure the file is deleted after the test + + // Initialize the Storage with the temporary file + storage = new Storage(tempFile.getAbsolutePath()); + + // Clear static managers before each test to ensure tests are isolated + ExpenseManager.reset(); + IncomeManager.reset(); + BudgetManager.reset(); + } + + @AfterEach + public void tearDown() { + // Cleanup any resources after each test + tempFile.delete(); + } + + @Test + public void testCreateFileIfNotExists_createsFile() throws IOException { + // Ensure the file doesn't exist before creating it + File file = new File(tempFile.getAbsolutePath()); + file.delete(); // Delete if it exists + + // Ensure the file doesn't exist + assertFalse(file.exists()); + + // Call the method to create the file + storage.createFileIfNotExists(); + + // Verify the file has been created + assertTrue(file.exists()); + } + + @Test + public void testLoad_fileNotFound_throwsFileNotFoundException() { + // Use a non-existing file path + Storage invalidStorage = new Storage("non_existing_file.txt"); + + // Assert that load() throws FileNotFoundException + assertThrows(IOException.class, invalidStorage::load); + } + + @Test + public void testLoad_successfulLoading() throws IOException { + try (FileWriter writer = new FileWriter(tempFile)) { + writer.write("expense | Lunch | 10.0 | 10/10/2024 | Food\n"); + writer.write("income | Salary | 2000.0 | 01/10/2024\n"); + writer.write("budget | 1500.0 | 2024-10 | {Food=500.0, Transport=300.0}\n"); + } + storage.load(); + assertDoesNotThrow(() -> storage.load()); + } + + @Test + public void testSave_savesCorrectly() throws IOException { + //Sample Data for Testing + Expense expense = new Expense("Lunch", 10.0, LocalDate.of(2024, 7, 10), + Category.FOOD); + Income income = new Income("Salary", 2000.0, LocalDate.of(2024, 7, 10)); + Budget budget = new Budget(YearMonth.of(2024, 7)); + + // Add sample data to static managers + ExpenseManager.addExpense(expense); + IncomeManager.addIncome(income); + BudgetManager.addBudget(budget); + + // Save data to the file + storage.save(ExpenseManager.getInstance(), IncomeManager.getInstance(), BudgetManager.getInstance()); + + // Read the content from the file and check if the data was saved correctly + File file = new File(tempFile.getAbsolutePath()); + assertTrue(file.exists()); + + // Perform actual file reading and check if the saved data matches the expected format + Scanner scanner = new Scanner(file); + boolean expenseSaved = false; + boolean incomeSaved = false; + boolean budgetSaved = false; + + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (line.contains("Lunch")) { + expenseSaved = true; + } + if (line.contains("Salary")) { + incomeSaved = true; + } + if (line.contains("2024-07")) { + budgetSaved = true; + } + + } + + // Ensure all data is saved + assertTrue(expenseSaved); + assertTrue(incomeSaved); + assertTrue(budgetSaved); + } +} + diff --git a/src/test/java/seedu/budgetbuddy/commands/budget/AddBudgetCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/budget/AddBudgetCommandTest.java new file mode 100644 index 0000000000..37a7d93da7 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/budget/AddBudgetCommandTest.java @@ -0,0 +1,93 @@ +package seedu.budgetbuddy.commands.budget; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AddBudgetCommandTest { + + @BeforeEach + public void setUp() { + // Reset the static state of BudgetManager before each test + BudgetManager.reset(); + } + + @Test + public void constructor_validInputs_initializesCorrectly() { + AddBudgetCommand command = new AddBudgetCommand(100.0, + YearMonth.of(2024, 10), Category.FOOD); + assertNotNull(command); + } + + @Test + public void constructor_inputIsNegativeAmount_throwsAssertionError() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + new AddBudgetCommand(-50.0, YearMonth.of(2024, 10), Category.FOOD); + }); + assertEquals("Amount must be non-negative", exception.getMessage()); + } + + @Test + public void constructor_inputIsNullDate_throwsAssertionError() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + new AddBudgetCommand(100.0, null, Category.FOOD); + }); + assertEquals("Date cannot be null", exception.getMessage()); + } + + @Test + public void constructor_inputIsNullCategory_throwsAssertionError() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + new AddBudgetCommand(100.0, YearMonth.of(2024, 10), null); + }); + assertEquals("Category cannot be null", exception.getMessage()); + } + + @Test + public void isCommand_commandStartsWithAddBudget_returnsTrue() { + assertTrue(AddBudgetCommand.isCommand("add budget 100 for October 2024")); + } + + @Test + public void isCommand_commandDoesNotStartWithAddBudget_returnsFalse() { + assertFalse(AddBudgetCommand.isCommand("remove budget 100 for October 2024")); + } + + @Test + public void execute_noExistingBudget_addsNewBudget() { + AddBudgetCommand command = new AddBudgetCommand(150.0, + YearMonth.of(2024, 10), Category.FOOD); + command.execute(); + + Budget budget = BudgetManager.getBudget(YearMonth.of(2024, 10)); + assertNotNull(budget); + assertEquals(150.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void execute_existingBudget_updatesBudget() { + // Add a budget + AddBudgetCommand addCommand = new AddBudgetCommand(100.0, + YearMonth.of(2024, 10), Category.FOOD); + addCommand.execute(); + + // Add another amount to the existing budget + AddBudgetCommand updateCommand = new AddBudgetCommand(50.0, + YearMonth.of(2024, 10), Category.FOOD); + updateCommand.execute(); + + Budget budget = BudgetManager.getBudget(YearMonth.of(2024, 10)); + assertNotNull(budget); + assertEquals(150.0, budget.getTotalMonthlyBudget()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommandTest.java new file mode 100644 index 0000000000..910915d472 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/budget/DeductBudgetCommandTest.java @@ -0,0 +1,90 @@ +package seedu.budgetbuddy.commands.budget; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DeductBudgetCommandTest { + + @BeforeEach + public void setUp() { + // Reset the static state of BudgetManager before each test + BudgetManager.reset(); + } + + @Test + public void constructor_validInputs_initializesCorrectly() { + DeductBudgetCommand command = new DeductBudgetCommand(50.0, + YearMonth.of(2024, 10), Category.FOOD); + assertNotNull(command); + } + + @Test + public void constructor_inputISNegativeAmount_throwsAssertionError() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + new DeductBudgetCommand(-30.0, YearMonth.of(2024, 10), Category.FOOD); + }); + assertEquals("Amount must be non-negative", exception.getMessage()); + } + + @Test + public void constructor_inputIsNullDate_throwsAssertionError() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + new DeductBudgetCommand(50.0, null, Category.FOOD); + }); + assertEquals("Date cannot be null", exception.getMessage()); + } + + @Test + public void constructor_inputIsNullCategory_throwsAssertionError() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + new DeductBudgetCommand(50.0, YearMonth.of(2024, 10), null); + }); + assertEquals("Category cannot be null", exception.getMessage()); + } + + @Test + public void isCommand_commandStartsWithDeductBudget_returnsTrue() { + assertTrue(DeductBudgetCommand.isCommand("deduct budget 50 for October 2024")); + } + + @Test + public void isCommand_commandDoesNotStartWithDeductBudget_returnsFalse() { + assertFalse(DeductBudgetCommand.isCommand("add budget 50 for October 2024")); + } + + @Test + public void execute_existingBudget_deductsAmount() { + // First, add a budget to deduct from + AddBudgetCommand addCommand = new AddBudgetCommand(100.0, + YearMonth.of(2024, 10), Category.FOOD); + addCommand.execute(); + + // Now deduct an amount from the existing budget + DeductBudgetCommand deductCommand = new DeductBudgetCommand(30.0, + YearMonth.of(2024, 10), Category.FOOD); + deductCommand.execute(); + + Budget budget = BudgetManager.getBudget(YearMonth.of(2024, 10)); + assertNotNull(budget); + assertEquals(70.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void execute_noExistingBudget_doesNothing() { + DeductBudgetCommand deductCommand = new DeductBudgetCommand(50.0, + YearMonth.of(2024, 10), Category.FOOD); + assertNull(BudgetManager.getBudget(YearMonth.of(2024, 10))); // Ensure no budget was created + } +} diff --git a/src/test/java/seedu/budgetbuddy/commands/budget/ListBudgetCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/budget/ListBudgetCommandTest.java new file mode 100644 index 0000000000..7d2d489e94 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/budget/ListBudgetCommandTest.java @@ -0,0 +1,72 @@ +package seedu.budgetbuddy.commands.budget; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.budget.BudgetManager; +import seedu.budgetbuddy.transaction.budget.Budget; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ListBudgetCommandTest { + + private BudgetManager budgetManager; + private ListBudgetCommand listBudgetCommand; + + @BeforeEach + public void setUp() { + budgetManager = new BudgetManager(); + } + + @AfterEach + public void tearDown() { + budgetManager.reset(); // Clear data after each test + } + + @Test + public void isCommand_validCommand_true() { + assertTrue(ListBudgetCommand.isCommand("list budgets")); + } + + @Test + public void isCommand_invalidCommand_false() { + assertFalse(ListBudgetCommand.isCommand("add budget")); + } + + @Test + public void constructor_withDate_setsDateCorrectly() { + YearMonth date = YearMonth.of(2024, 11); + listBudgetCommand = new ListBudgetCommand(date); + assertEquals(date, listBudgetCommand.getDate()); + } + + @Test + public void execute_withSpecificDate_listsBudgetsForThatDate() { + YearMonth date = YearMonth.of(2024, 11); + Budget budget = new Budget(date); + budgetManager.addBudget(budget); + + listBudgetCommand = new ListBudgetCommand(date); + + listBudgetCommand.execute(); + + assertEquals(1, budgetManager.getNumberOfBudgets()); + assertEquals(budget, budgetManager.getBudget(date)); + } + + @Test + public void execute_withNullDate_listsAllBudgets() { + budgetManager.addBudget(new Budget(YearMonth.of(2024, 11))); + budgetManager.addBudget(new Budget(YearMonth.of(2024, 10))); + + listBudgetCommand = new ListBudgetCommand(null); + + listBudgetCommand.execute(); + + assertEquals(2, budgetManager.getNumberOfBudgets()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/commands/expense/AddExpenseCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/expense/AddExpenseCommandTest.java new file mode 100644 index 0000000000..9b437cec94 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/expense/AddExpenseCommandTest.java @@ -0,0 +1,66 @@ +package seedu.budgetbuddy.commands.expense; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AddExpenseCommandTest { + + @BeforeEach + public void setUp() { + ExpenseManager.reset(); + } + + @Test + public void constructor_validInputs_initializesCorrectly() { + AddExpenseCommand command = new AddExpenseCommand("Lunch", 15.0, LocalDate.of(2024, + 10, 20), Category.FOOD); + assertNotNull(command); + assertEquals("Lunch", command.getDescription()); + assertEquals(15.0, command.getAmount()); + assertEquals(LocalDate.of(2024, 10, 20), command.getDate()); + assertEquals(Category.FOOD, command.getCategory()); + } + + @Test + public void isCommand_commandStartsWithAddExpense_returnsTrue() { + assertTrue(AddExpenseCommand.isCommand("add expense Lunch 15 for FOOD")); + } + + @Test + public void isCommand_commandDoesNotStartWithAddExpense_returnsFalse() { + assertFalse(AddExpenseCommand.isCommand("remove expense Lunch 15 for FOOD")); + } + + @Test + public void execute_validExpense_addsExpense() { + AddExpenseCommand command = new AddExpenseCommand("Dinner", 20.0, LocalDate.of(2024, + 10, 20), Category.FOOD); + command.execute(); + + Expense expense = ExpenseManager.getExpenses().get(0); // Assume this is the first added expense + assertNotNull(expense); + assertEquals("Dinner", expense.getDescription()); + assertEquals(20.0, expense.getAmount()); + assertEquals(LocalDate.of(2024, 10, 20), expense.getDate()); + assertEquals(Category.FOOD, expense.getCategory()); + } + + @Test + public void execute_nullDate_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> { + new AddExpenseCommand("Groceries", 10.0, null, Category.OTHERS).execute(); + }); + } +} + diff --git a/src/test/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommandTest.java new file mode 100644 index 0000000000..5a77a37ea1 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/expense/DeleteExpenseCommandTest.java @@ -0,0 +1,56 @@ +package seedu.budgetbuddy.commands.expense; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DeleteExpenseCommandTest { + + @BeforeEach + public void setUp() { + ExpenseManager.reset(); + //Add Sample Data to test with + ExpenseManager.addExpense(new Expense("Lunch", 10.0, LocalDate.now(), Category.FOOD)); + ExpenseManager.addExpense(new Expense("Bus fare", 2.5, LocalDate.now(), Category.TRANSPORT)); + } + + @Test + public void execute_validIndex_deletesExpense() { + // Assert initial size of expense list + assertEquals(2, ExpenseManager.getExpenses().size()); + + // Delete the first expense + DeleteExpenseCommand deleteCommand = new DeleteExpenseCommand(0); + deleteCommand.execute(); + + // Verify the size of expense list after deletion + assertEquals(1, ExpenseManager.getExpenses().size()); + assertEquals("Bus fare", ExpenseManager.getExpenses().get(0).getDescription()); + } + + @Test + public void execute_invalidIndex_throwsIndexOutOfBoundsException() { + DeleteExpenseCommand deleteCommand = new DeleteExpenseCommand(5); + + assertThrows(IndexOutOfBoundsException.class, deleteCommand::execute); + } + + @Test + public void isCommand_commandStartsWithDeleteExpense_returnsTrue() { + assertTrue(DeleteExpenseCommand.isCommand("delete expense 1")); + } + + @Test + public void isCommand_commandDoesNotStartWithDeleteExpense_returnsFalse() { + assertFalse(DeleteExpenseCommand.isCommand("remove expense 1")); + } +} diff --git a/src/test/java/seedu/budgetbuddy/commands/expense/EditExpenseCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/expense/EditExpenseCommandTest.java new file mode 100644 index 0000000000..b7f10cc71d --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/expense/EditExpenseCommandTest.java @@ -0,0 +1,62 @@ +package seedu.budgetbuddy.commands.expense; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EditExpenseCommandTest { + private static Expense expense; + private static final double EMPTY_AMOUNT = -1.0; + private static final Category EMPTY_CATEGORY = null; + private static final LocalDate EMPTY_DATE = null; + + @Test + void editExpensesWithAmountOnly_validAmount_expectChangedAmount(){ + expense = new Expense( + "New Expense", + 500.0, + LocalDate.of(2024,2,12), + Category.FOOD); + double newAmount = 50; + EditExpenseCommand expenseCommand = new EditExpenseCommand(expense); + expenseCommand.setAmount(newAmount); + expenseCommand.processEdit(); + assertEquals(newAmount,expense.getAmount()); + } + + @Test + void editExpensesWithCategoryOnly_validCategory_expectChangedCategory(){ + expense = new Expense( + "New Expense", + 500.0, + LocalDate.of(2024,2,12), + Category.FOOD); + Category newCategory = Category.TRANSPORT; + EditExpenseCommand expenseCommand = new EditExpenseCommand(expense); + expenseCommand.setCategory(newCategory); + expenseCommand.processEdit(); + assertEquals(newCategory,expense.getCategory()); + } + + @Test + void editExpensesWithEmptyFields_expectUnchangedExpense(){ + expense = new Expense( + "New Expense", + 500.0, + LocalDate.of(2024,2,12), + Category.FOOD); + EditExpenseCommand expenseCommand = new EditExpenseCommand(expense); + expenseCommand.setCategory(EMPTY_CATEGORY); + expenseCommand.setDate(EMPTY_DATE); + expenseCommand.setAmount(EMPTY_AMOUNT); + expenseCommand.processEdit(); + assertEquals(Category.FOOD,expense.getCategory()); + assertEquals(LocalDate.of(2024,2,12),expense.getDate()); + assertEquals(500.0,expense.getAmount()); + + } +} diff --git a/src/test/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommandTest.java b/src/test/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommandTest.java new file mode 100644 index 0000000000..a751dff207 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/commands/income/DisplayIncomeSpentCommandTest.java @@ -0,0 +1,84 @@ +package seedu.budgetbuddy.commands.income; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.time.LocalDate; +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.budgetbuddy.transaction.Category.OTHERS; + +public class DisplayIncomeSpentCommandTest { + + private DisplayIncomeSpentCommand commandForNovember; + private DisplayIncomeSpentCommand commandForOctober; + private ByteArrayOutputStream outputStreamCaptor; + + @BeforeEach + public void setUp() { + // Set up a new ByteArrayOutputStream to capture output + outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + + // Temporarily suppress system output when adding expenses and incomes + PrintStream originalSystemOut = System.out; + System.setOut(new PrintStream(new ByteArrayOutputStream())); // Suppress output + + // Clear existing incomes and expenses if needed + IncomeManager.getIncomes().clear(); + ExpenseManager.getExpenses().clear(); + + // Set up dummy incomes for specific months + IncomeManager.addIncome(new Income("Salary", 2000.0, LocalDate.of(2024, 11, 5))); + IncomeManager.addIncome(new Income("Freelance Work", 500.0, LocalDate.of(2024, 11, 10))); + IncomeManager.addIncome(new Income("Gift", 3000.0, LocalDate.of(2024, 10, 3))); + + // Set up dummy expenses for specific months + ExpenseManager.addExpense(new Expense("Groceries", 1000.0, LocalDate.of(2024, 11, 6), OTHERS)); + ExpenseManager.addExpense(new Expense("Utilities", 500.0, LocalDate.of(2024, 11, 15), OTHERS)); + ExpenseManager.addExpense(new Expense("Dining Out", 900.0, LocalDate.of(2024, 10, 10), OTHERS)); + + // Restore original System.out + System.setOut(originalSystemOut); + + // Initialize commands with YearMonth to test + commandForNovember = new DisplayIncomeSpentCommand(YearMonth.of(2024, 11)); + commandForOctober = new DisplayIncomeSpentCommand(YearMonth.of(2024, 10)); + } + + @Test + public void isCommand_validCommand_returnsTrue() { + assertTrue(DisplayIncomeSpentCommand.isCommand("display income spent")); + } + + @Test + public void isCommand_invalidCommand_returnsFalse() { + assertFalse(DisplayIncomeSpentCommand.isCommand("deduct income spent")); + } + + @Test + public void execute_correctMonthPercentageDisplayed() { + // Execute command and check display output + commandForNovember.execute(); + + String output = outputStreamCaptor.toString().trim(); + assertTrue(output.contains("Percentage of income spent for 2024-11: 60.0%")); + } + + @Test + public void execute_differentMonthPercentageDisplayed() { + // Execute command and check display output + commandForOctober.execute(); + + String output = outputStreamCaptor.toString().trim(); + assertTrue(output.contains("Percentage of income spent for 2024-10: 30.0%")); + } +} diff --git a/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetManagerTest.java new file mode 100644 index 0000000000..c1b04311eb --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetManagerTest.java @@ -0,0 +1,99 @@ +package seedu.budgetbuddy.transaction.budget; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BudgetManagerTest { + + @BeforeEach + public void setUp() { + // Reset the static state of BudgetManager before each test + BudgetManager.reset(); + } + + @Test + public void testAddBudget_inputIsBudget_incrementsNumberOfBudgets() { + Budget budget = new Budget(YearMonth.of(2024, 1)); + BudgetManager.addBudget(budget); + assertEquals(1, BudgetManager.getNumberOfBudgets()); + } + + @Test + public void testAddBudget_inputIsBudget_addsBudgetToList() { + Budget budget = new Budget(YearMonth.of(2024, 7)); + BudgetManager.addBudget(budget); + assertEquals(budget, BudgetManager.getBudgets().get(0)); + } + + @Test + public void testDeleteBudget_inputIsBudget_decrementsNumberOfBudgets() { + Budget budget = new Budget(YearMonth.of(2024, 5)); + BudgetManager.addBudget(budget); + BudgetManager.deleteBudget(budget); + assertEquals(0, BudgetManager.getNumberOfBudgets()); + } + + @Test + public void testDeleteBudget_inputIsBudget_deleteBudgetFromList() { + Budget budget = new Budget(YearMonth.of(2024, 3)); + BudgetManager.addBudget(budget); + BudgetManager.deleteBudget(budget); + assertTrue(BudgetManager.getBudgets().isEmpty()); + } + + @Test + public void testGetBudget_returnsCorrectBudget() { + Budget budget = new Budget(YearMonth.of(2024, 8)); + BudgetManager.addBudget(budget); + Budget result = BudgetManager.getBudget(YearMonth.of(2024, 8)); + assertEquals(budget, result); + } + + @Test + public void testGetBudget_returnsNullForNonExistentBudget() { + Budget result = BudgetManager.getBudget(YearMonth.of(2024, 12)); + assertNull(result); + } + + @Test + public void addBudget_inputIsNullBudget_assertionErrorThrown() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + BudgetManager.addBudget(null); + }); + assertEquals("Budget to be added cannot be null", exception.getMessage()); + } + + @Test + public void deleteBudget_inputIsNullBudget_assertionErrorThrown() { + AssertionError exception = assertThrows(AssertionError.class, () -> { + BudgetManager.deleteBudget(null); + }); + assertEquals("Budget to be deleted cannot be null", exception.getMessage()); + } + + @Test + public void getBudget_multipleBudgets_returnsCorrectBudget() { + Budget budget1 = new Budget(YearMonth.of(2024, 9)); + Budget budget2 = new Budget(YearMonth.of(2024, 8)); + BudgetManager.addBudget(budget1); + BudgetManager.addBudget(budget2); + Budget result = BudgetManager.getBudget(YearMonth.of(2024, 9)); + assertEquals(budget1, result); + } + + @Test + public void reset_clearsBudgets() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + BudgetManager.addBudget(budget); + BudgetManager.reset(); + assertEquals(0, BudgetManager.getNumberOfBudgets()); + assertTrue(BudgetManager.getBudgets().isEmpty()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetTest.java b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetTest.java new file mode 100644 index 0000000000..54d334f062 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/transaction/budget/BudgetTest.java @@ -0,0 +1,143 @@ +package seedu.budgetbuddy.transaction.budget; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BudgetTest { + + @Test + public void testAddAmount_inputIsDouble_increasesBudgetTotalMonthlyBudget() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 50.0); + assertEquals(50.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void testDeductAmount_inputIsDouble_decreasesBudgetTotalMonthlyBudget() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.OTHERS, 100); + budget.deductAmount(Category.OTHERS, 30.0); + assertEquals(70.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void testAddAmount_inputIsInt_increasesBudgetTotalMonthlyBudget() { + Budget budget = new Budget(YearMonth.of(2024, 2)); + budget.addAmount(Category.ENTERTAINMENT, 150); + assertEquals(150.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void testDeductAmount_inputIsInt_decreasesBudgetTotalMonthlyBudget() { + Budget budget = new Budget(YearMonth.of(2024, 2)); + budget.addAmount(Category.TRANSPORT, 100); + budget.deductAmount(Category.TRANSPORT, 70); + assertEquals(30.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void testToString_returnsCorrectString() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 100); + String result = budget.toString(); + assertEquals("Total Monthly Budget: 100.00 Date: 2024-10\n Category: {FOOD=100.00}", result); + } + + @Test + public void testGetAmount_returnsCorrectTotalMonthlyBudget() { + Budget budget = new Budget(YearMonth.of(2024, 9)); + budget.addAmount(Category.FOOD, 250); + assertEquals(250.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void testGetDate_returnsCorrectDate() { + Budget budget = new Budget(YearMonth.of(2024, 9)); + assertEquals(YearMonth.of(2024, 9), budget.getDate()); + } + + @Test + public void addAmount_inputIsValidCategory_updatesTotalCorrectly() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 50.0); + assertEquals(50.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void deductAmount_inputIsValidCategory_decreasesTotalMonthlyBudget() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.OTHERS, 100); + budget.deductAmount(Category.OTHERS, 30.0); + assertEquals(70.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void addMultipleCategories_updatesTotalCorrectly() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 50); + budget.addAmount(Category.ENTERTAINMENT, 150); + budget.addAmount(Category.TRANSPORT, 100); + assertEquals(300.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void deductBudget_inputIsExceedingAmount_removesCategory() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 50); + budget.deductAmount(Category.FOOD, 50); + assertTrue(budget.getCategoryBudgets().isEmpty()); + } + + @Test + public void deductBudget_inputIsNonExistentCategory_doesNotChangeBudget() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.deductAmount(Category.FOOD, 10); + assertEquals(0.0, budget.getTotalMonthlyBudget()); + assertTrue(budget.getCategoryBudgets().isEmpty()); + } + + @Test + public void addBudget_inputIsZeroAmount_doesNotChangeBudget() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 0); + assertEquals(0.0, budget.getTotalMonthlyBudget()); + } + + @Test + public void deepCopyConstructor_copiesBudgetCorrectly() { + Budget original = new Budget(YearMonth.of(2024, 10)); + original.addAmount(Category.FOOD, 100); + Budget copy = new Budget(original); + original.deductAmount(Category.FOOD, 50); // Change the original + + assertEquals(50.0, original.getTotalMonthlyBudget()); // Original should be updated + assertEquals(100.0, copy.getTotalMonthlyBudget()); // Copy should remain unchanged + } + + @Test + public void toString_returnsCorrectStringRepresentation() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 100); + String result = budget.toString(); + assertEquals("Total Monthly Budget: 100.00 Date: 2024-10\n Category: {FOOD=100.00}", result); + } + + @Test + public void getDate_returnsCorrectDate() { + Budget budget = new Budget(YearMonth.of(2024, 9)); + assertEquals(YearMonth.of(2024, 9), budget.getDate()); + } + + @Test + public void addAmount_inputIsExistingCategory_updatesAmount() { + Budget budget = new Budget(YearMonth.of(2024, 10)); + budget.addAmount(Category.FOOD, 100); + budget.addAmount(Category.FOOD, 50); // Adding to existing category + assertEquals(150.0, budget.getTotalMonthlyBudget()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManagerTest.java new file mode 100644 index 0000000000..6b064b76c0 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/transaction/budget/RemainingBudgetManagerTest.java @@ -0,0 +1,62 @@ +package seedu.budgetbuddy.transaction.budget; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.time.LocalDate; +import java.time.YearMonth; + +/** + * Test class for RemainingBudgetManager. + */ +public class RemainingBudgetManagerTest { + + private RemainingBudgetManager remainingBudgetManager; + + @BeforeEach + public void setUp() { + // Set up budgets and expenses + BudgetManager.reset(); + ExpenseManager.reset(); + Budget budgetOct = new Budget(YearMonth.of(2024, 10)); + budgetOct.addAmount(Category.EDUCATION, 500); + Budget budgetSep = new Budget(YearMonth.of(2024, 9)); + budgetSep.addAmount(Category.OTHERS, 300); + BudgetManager.addBudget(budgetOct); + BudgetManager.addBudget(budgetSep); + + ExpenseManager.addExpense(new Expense("Lunch", 50.0, LocalDate.of(2024, 11, 5), Category.FOOD)); + ExpenseManager.addExpense(new Expense("Groceries", 30.0, LocalDate.of(2024, 9, 25), Category.OTHERS)); + + remainingBudgetManager = new RemainingBudgetManager(); // Initialize + } + + @Test + public void getRemainingBudgets_expenseWithoutBudget_negativeBudget() { + String result = remainingBudgetManager.getRemainingBudgets(LocalDate.of(2024, 11, 5), Category.FOOD); + assertEquals("The remaining budget for 2024-11 in the FOOD category is: -50.00\n" + + "Caution! You have exceeded your budget!", result); + } + + @Test + public void getRemainingBudgets_expenseWithBudget_positiveBudget() { + String result = remainingBudgetManager.getRemainingBudgets(LocalDate.of(2024, 9, 25), Category.OTHERS); + assertEquals("The remaining budget for 2024-09 in the OTHERS category is: 270.00", result); + } + + @Test + public void getRemainingBudgets_addExpense_positiveBudget() { + ExpenseManager.addExpense(new Expense("Books", 20.0, LocalDate.of(2024, 10, 5), Category.EDUCATION)); + remainingBudgetManager = new RemainingBudgetManager(); + + String result = remainingBudgetManager.getRemainingBudgets(LocalDate.of(2024, 10, 5), Category.EDUCATION); + assertEquals("The remaining budget for 2024-10 in the EDUCATION category is: 480.00", result); + } + +} diff --git a/src/test/java/seedu/budgetbuddy/transaction/expense/ExpenseManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/expense/ExpenseManagerTest.java new file mode 100644 index 0000000000..64eb4dbf33 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/transaction/expense/ExpenseManagerTest.java @@ -0,0 +1,174 @@ +package seedu.budgetbuddy.transaction.expense; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExpenseManagerTest { + + private static int numberOfExpenses = 0; + private static ArrayList expenses = new ArrayList<>(); + private static ExpenseManager expenseManager; + private static final String EMPTY_DISPLAY_STRING = + "No expense entry with given parameters found, try again with a different parameter."; + + void initializeTestContent(){ + expenseManager = new ExpenseManager(expenses, numberOfExpenses); + expenseManager.reset(); + Expense newExpense = new Expense("New Food", + 12, + LocalDate.parse("2024-02-12"), + Category.FOOD); + expenseManager.addExpense(newExpense); + } + void initializeEmptyTestContent(){ + expenseManager = new ExpenseManager(new ArrayList<>(), numberOfExpenses); + } + + String getExpectedString(){ + String result = ""; + int counter = 1; + for (Expense expense : ExpenseManager.getExpenses()) { + result += counter + ". " + expense.toString() + "\n"; + counter++; + } + result += "Your total expense(s) for FOOD in February 2024 are $12.00"; + return result; + } + + @Test + void displayExpensesWithCategoryAndDate_validMonthCategory_expectOneEntry() { + initializeTestContent(); + Category category = Category.FOOD; + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals(getExpectedString(), + ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth)); + } + + @Test + void displayExpensesWithCategoryAndDate_incorrectCategory_expectEmptyDisplayString() { + Category category = Category.TRANSPORT; + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals(EMPTY_DISPLAY_STRING, + ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth)); + } + + @Test + void displayExpensesWithCategoryAndDate_incorrectMonth_expectEmptyDisplayString() { + Category category = Category.FOOD; + YearMonth yearMonth = YearMonth.of(2024, 1); + assertEquals(EMPTY_DISPLAY_STRING, + ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth)); + } + + @Test + void searchExpense_noDescriptor_expectEmptyDisplayString(){ + initializeTestContent(); + assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.searchExpenses("")); + } + + @Test + void searchExpense_descriptorProvidedNoFoundExpense_expectEmptyDisplayString(){ + initializeTestContent(); + assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.searchExpenses("RANDOMMESSAGE")); + } + + @Test + void searchExpense_foundExpense_expectOutput(){ + initializeTestContent(); + assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n", + ExpenseManager.searchExpenses("New")); + } + + @Test + void breakdownExpensesByCategory_noExpenses_expectNoExpensesMessage(){ + initializeEmptyTestContent(); + assertEquals("Total expenses: 0. You have not indicated any expense yet.", + ExpenseManager.breakdownExpensesByCategory()); + } + + @Test + void breakdownExpensesByCategory_oneExpense_expectExpenseMessage(){ + initializeTestContent(); + assertEquals("Total expenses: 12.0\nFood: 12.0(100.00%)\nTransport: 0.0(0.00%)\nUtilities:" + + " 0.0(0.00%)\nEntertainment: 0.0(0.00%)\nEducation: 0.0(0.00%)\nOthers: 0.0(0.00%)\n", + ExpenseManager.breakdownExpensesByCategory()); + } + + @Test + void listExpensesWithCategoryAndDate_noExpenses_expectNoExpensesMessage(){ + initializeEmptyTestContent(); + Category category = Category.FOOD; + YearMonth yearMonth = YearMonth.now(); + assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth)); + } + + @Test + void listExpensesWithCategoryAndDate_oneExpense_expectExpenseMessage(){ + initializeTestContent(); + Category category = Category.FOOD; + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n" + + "Your total expense(s) for FOOD in February 2024 are $12.00", + ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth)); + } + + @Test + void listExpensesWithCategoryAndDate_oneExpenseWrongCategory_expectNoExpensesFoundMessage(){ + initializeTestContent(); + Category category = Category.ENTERTAINMENT; + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithCategoryAndDate(category, yearMonth)); + } + + @Test + void listExpensesWithCategory_noExpense_expectNoExpensesFoundMessage(){ + initializeEmptyTestContent(); + Category category = Category.FOOD; + assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithCategory(category)); + } + + @Test + void listExpensesWithCategory_oneExpense_expectExpenseMessage(){ + initializeTestContent(); + Category category = Category.FOOD; + assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n" + + "Your total expense(s) for FOOD are $12.00", + ExpenseManager.listExpensesWithCategory(category)); + } + + @Test + void listExpensesWithDate_noExpense_expectNoExpensesFoundMessage(){ + initializeEmptyTestContent(); + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals(EMPTY_DISPLAY_STRING, ExpenseManager.listExpensesWithDate(yearMonth)); + } + + @Test + void listExpensesWithDate_oneExpense_expectExpenseMessage(){ + initializeTestContent(); + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals("1. Description: New Food Amount: 12.00 Date: 2024-02-12 Category: FOOD\n" + + "Your total expense(s) for February 2024 are $12.00", + ExpenseManager.listExpensesWithDate(yearMonth)); + } + + @Test + void getMonthlyExpense_oneExpense_expectMonthlyExpense(){ + initializeTestContent(); + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals(12.0, ExpenseManager.getMonthlyExpense(yearMonth)); + } + + @Test + void getMonthlyExpense_noExpense_expectZeroMonthlyExpense(){ + initializeEmptyTestContent(); + YearMonth yearMonth = YearMonth.of(2024, 2); + assertEquals(0.0, ExpenseManager.getMonthlyExpense(yearMonth)); + } +} diff --git a/src/test/java/seedu/budgetbuddy/transaction/income/IncomeManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/income/IncomeManagerTest.java new file mode 100644 index 0000000000..7893d2301c --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/transaction/income/IncomeManagerTest.java @@ -0,0 +1,46 @@ +package seedu.budgetbuddy.transaction.income; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class IncomeManagerTest { + + @BeforeEach + void resetIncomeManager() { + IncomeManager.reset(); // Ensure a clean state before each test + } + + @Test + void addIncome_validIncome_incomeAddedSuccessfully() { + Income income = new Income("Salary", 5000, LocalDate.of(2024, 10, 1)); + IncomeManager.addIncome(income); + + assertEquals(1, IncomeManager.getNumberOfIncomes()); + assertEquals(income, IncomeManager.getIncomes().get(0)); + } + + @Test + void deleteIncome_validIndex_incomeDeletedSuccessfully() { + Income income = new Income("Salary", 5000, LocalDate.of(2024, 10, 1)); + IncomeManager.addIncome(income); + + IncomeManager.deleteIncome(0); + + assertEquals(0, IncomeManager.getNumberOfIncomes()); + assertTrue(IncomeManager.getIncomes().isEmpty()); + } + + @Test + void getYearMonthFromDate_validDate_returnsCorrectYearMonth() { + LocalDate date = LocalDate.of(2024, 10, 1); + YearMonth expected = YearMonth.of(2024, 10); + assertEquals(expected, IncomeManager.getYearMonthFromDate(date)); + } +} + diff --git a/src/test/java/seedu/budgetbuddy/transaction/saving/SavingsManagerTest.java b/src/test/java/seedu/budgetbuddy/transaction/saving/SavingsManagerTest.java new file mode 100644 index 0000000000..f1c87942c7 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/transaction/saving/SavingsManagerTest.java @@ -0,0 +1,73 @@ +package seedu.budgetbuddy.transaction.saving; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.time.LocalDate; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SavingsManagerTest { + private static IncomeManager incomeManager; + private static ExpenseManager expenseManager; + private static ArrayList incomes; + private static ArrayList expenses; + + void initializeTestContent(){ + incomes = new ArrayList<>(); + int numberOfIncomes = 0; + incomeManager = new IncomeManager(incomes, numberOfIncomes); + expenses = new ArrayList<>(); + int numberOfExpenses = 0; + expenseManager = new ExpenseManager(expenses, numberOfExpenses); + } + + void initializeTestContent(boolean emptyIncome, boolean emptyExpense){ + incomes = new ArrayList<>(); + int numberOfIncomes = 0; + incomeManager = new IncomeManager(incomes, numberOfIncomes); + if (!emptyIncome){ + Income newIncome = new Income("tuition", 1000, LocalDate.parse("2024-10-10")); + incomeManager.addIncome(newIncome); + } + expenses = new ArrayList<>(); + int numberOfExpenses = 0; + expenseManager = new ExpenseManager(expenses, numberOfExpenses); + if (!emptyExpense){ + Expense newExpense = new Expense("Ticket", 100, LocalDate.parse("2024-10-10"), + Category.ENTERTAINMENT); + expenseManager.addExpense(newExpense); + } + } + + @Test + void displayTotalSavings_noIncomeNoExpense_expectZeroSavings(){ + initializeTestContent(); + assertEquals("Total Savings: 0.00\nTotal Income: 0.00\nTotal Expense: 0.00", + SavingsManager.displayTotalSavings()); + } + + @Test + void displayTotalSavings_noIncomeOneExpense_expectNegativeSavings(){ + initializeTestContent(true, false); + assertEquals("Total Savings: -100.00\nTotal Income: 0.00\nTotal Expense: 100.00", + SavingsManager.displayTotalSavings()); + } + + @Test + void displayTotalSavingsByMonth_noIncomeNoExpense_expectZeroSavings(){ + initializeTestContent(); + assertEquals("Total savings: 0.00", SavingsManager.displayTotalSavingsByMonth()); + } + + @Test + void displayTotalSavingsByMonth_oneIncomeOneExpense_expectSavings(){ + initializeTestContent(false, false); + assertEquals("Savings in 2024-10: 900.00", SavingsManager.displayTotalSavingsByMonth()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/budget/AddBudgetValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/budget/AddBudgetValidatorTest.java new file mode 100644 index 0000000000..a092ec57bf --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/budget/AddBudgetValidatorTest.java @@ -0,0 +1,83 @@ +package seedu.budgetbuddy.validators.budget; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.budget.AddBudgetCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.Category; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AddBudgetValidatorTest { + + @Test + public void processCommand_validCommand_returnsAddBudgetCommand() throws BudgetBuddyException { + String command = "add budget a/100.0 m/11/2024 c/Food"; // Valid command + Command result = AddBudgetValidator.processCommand(command); + + assertNotNull(result); + assertTrue(result instanceof AddBudgetCommand); + AddBudgetCommand addBudgetCommand = (AddBudgetCommand) result; + assertEquals(100.0, addBudgetCommand.getAmount()); + assertEquals(YearMonth.of(2024, 11), addBudgetCommand.getDate()); + assertEquals(Category.FOOD, addBudgetCommand.getCategory()); + } + + @Test + public void processCommand_missingAmount_throwsBudgetBuddyException() { + String command = "add budget"; // Missing amount + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + AddBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("No amount provided.")); + } + + @Test + public void processCommand_invalidAmountFormat_throwsBudgetBuddyException() { + String command = "add budget a/invalid m/2024-11 c/Food"; // Invalid amount format + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + AddBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Invalid amount format.")); + } + + @Test + public void processCommand_negativeAmount_throwsBudgetBuddyException() { + String command = "add budget a/-100.0 m/2024-11 c/Food"; // Negative amount + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + AddBudgetValidator.processCommand(command)); + + + assertTrue(exception.getMessage().contains("Invalid amount: -100.0. Amount must be a positive value.")); + } + + @Test + public void processCommand_invalidDateFormat_throwsBudgetBuddyException() { + String command = "add budget a/100.0 m/invalid-date c/Food"; // Invalid date format + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + AddBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Invalid date format. Use m/MM/yyyy.")); + } + + @Test + public void processCommand_noCategory_usesDefaultCategory() throws BudgetBuddyException { + String command = "add budget a/100.0 m/11/2024"; // No category provided + + Command result = AddBudgetValidator.processCommand(command); + + assertNotNull(result); + assertTrue(result instanceof AddBudgetCommand); + AddBudgetCommand addBudgetCommand = (AddBudgetCommand) result; + assertEquals(Category.OTHERS, addBudgetCommand.getCategory()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidatorTest.java new file mode 100644 index 0000000000..23f2d7fd8e --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/budget/DeductBudgetValidatorTest.java @@ -0,0 +1,111 @@ +package seedu.budgetbuddy.validators.budget; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.budget.DeductBudgetCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.budget.Budget; +import seedu.budgetbuddy.transaction.budget.BudgetManager; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DeductBudgetValidatorTest { + + @BeforeEach + public void setUp() { + // Reset the static state of BudgetManager before each test + BudgetManager.reset(); + } + + @Test + public void processCommand_validCommand_returnsDeductBudgetCommand() throws BudgetBuddyException { + // Assuming a budget exists for November 2024 + Budget budget = new Budget(YearMonth.of(2024, 11)); + budget.addAmount(Category.FOOD, 1000); + BudgetManager.addBudget(budget); + + String command = "deduct budget a/100.0 m/11/2024 c/Food"; // Valid command + Command result = DeductBudgetValidator.processCommand(command); + + assertNotNull(result); + assertTrue(result instanceof DeductBudgetCommand); + DeductBudgetCommand deductBudgetCommand = (DeductBudgetCommand) result; + assertEquals(100.0, deductBudgetCommand.getAmount()); + assertEquals(YearMonth.of(2024, 11), deductBudgetCommand.getDate()); + assertEquals(Category.FOOD, deductBudgetCommand.getCategory()); + } + + @Test + public void processCommand_missingAmount_throwsBudgetBuddyException() { + String command = "deduct budget"; // Missing amount + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + DeductBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("No amount provided.")); + } + + @Test + public void processCommand_invalidAmountFormat_throwsBudgetBuddyException() { + String command = "deduct budget a/invalid m/2024-11 c/Food"; // Invalid amount format + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + DeductBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Invalid amount format.")); + } + + @Test + public void processCommand_negativeAmount_throwsBudgetBuddyException() { + String command = "deduct budget a/-100.0 m/11/2024 c/Food"; // Negative amount + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + DeductBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Invalid amount: -100.0. Must be a positive value.")); + } + + @Test + public void processCommand_invalidDateFormat_throwsBudgetBuddyException() { + String command = "deduct budget a/100.0 m/invalid-date c/Food"; // Invalid date format + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + DeductBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Invalid date format. Use m/MM/yyyy.")); + } + + @Test + public void processCommand_budgetDoesNotExist_throwsBudgetBuddyException() { + String command = "deduct budget a/100.0 m/11/2024 c/Food"; // Budget does not exist for the date + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + DeductBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Budget does not exist for the specified date: 2024-11")); + } + + @Test + public void processCommand_noCategory_usesDefaultCategory() throws BudgetBuddyException { + // Assuming a budget exists for November 2024 + Budget budget = new Budget(YearMonth.of(2024, 11)); + budget.addAmount(Category.OTHERS, 1000); + BudgetManager.addBudget(budget); + + String command = "deduct budget a/100.0 m/11/2024"; // No category provided + + Command result = DeductBudgetValidator.processCommand(command); + + assertNotNull(result); + assertTrue(result instanceof DeductBudgetCommand); + DeductBudgetCommand deductBudgetCommand = (DeductBudgetCommand) result; + assertEquals(Category.OTHERS, deductBudgetCommand.getCategory()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/budget/ListBudgetValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/budget/ListBudgetValidatorTest.java new file mode 100644 index 0000000000..13a8ea879d --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/budget/ListBudgetValidatorTest.java @@ -0,0 +1,49 @@ +package seedu.budgetbuddy.validators.budget; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.budget.ListBudgetCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ListBudgetValidatorTest { + + @Test + public void processCommand_noDate_returnsListBudgetCommand() throws BudgetBuddyException { + String command = "list budgets"; // No date provided + Command result = ListBudgetValidator.processCommand(command); + + assertNotNull(result); + assertEquals(ListBudgetCommand.class, result.getClass()); + ListBudgetCommand listBudgetCommand = (ListBudgetCommand) result; + assertNull(listBudgetCommand.getDate()); // Expecting null for date + } + + @Test + public void processCommand_validDate_returnsListBudgetCommand() throws BudgetBuddyException { + String command = "list budgets m/11/2024"; // Valid date provided + Command result = ListBudgetValidator.processCommand(command); + + assertNotNull(result); + assertEquals(ListBudgetCommand.class, result.getClass()); + ListBudgetCommand listBudgetCommand = (ListBudgetCommand) result; + assertEquals(YearMonth.of(2024, 11), listBudgetCommand.getDate()); + } + + @Test + public void processCommand_invalidDateFormat_throwsBudgetBuddyException() { + String command = "list budgets m/invalid-date"; // Invalid date format + + BudgetBuddyException exception = assertThrows(BudgetBuddyException.class, () -> + ListBudgetValidator.processCommand(command)); + + assertTrue(exception.getMessage().contains("Invalid format. Use 'list budgets [m/MM/yyyy]'.")); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/AddExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/AddExpenseValidatorTest.java new file mode 100644 index 0000000000..05fa322675 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/expense/AddExpenseValidatorTest.java @@ -0,0 +1,82 @@ +package seedu.budgetbuddy.validators.expense; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.expense.AddExpenseCommand; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.transaction.Category; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class AddExpenseValidatorTest { + + @Test + void processCommand_invalidAmount_returnsIncorrectCommand() { + Command result = AddExpenseValidator.processCommand("add expense Lunch a/abc d/12/10/2024 c/FOOD"); + + // Check if result is an IncorrectCommand and compare the message + assertInstanceOf(IncorrectCommand.class, result); + String message = ((IncorrectCommand) result).getFeedbackToUser(); + assertEquals("Amount should be a positive number with up to 2 decimal places.", message); + } + + @Test + void processCommand_noDescription_returnsIncorrectCommand() { + Command result = AddExpenseValidator.processCommand("add expense a/100 d/12/10/2024 c/FOOD"); + + // Check if result is an IncorrectCommand and compare the message + assertInstanceOf(IncorrectCommand.class, result); + String message = ((IncorrectCommand) result).getFeedbackToUser(); + assertEquals("Description cannot be empty.", message); + } + + @Test + void processCommand_invalidDate_returnsIncorrectCommand() { + Command result = AddExpenseValidator.processCommand("add expense Lunch a/100 d/invalid_date c/FOOD"); + + // Check if result is an IncorrectCommand and compare the message + assertInstanceOf(IncorrectCommand.class, result); + String message = ((IncorrectCommand) result).getFeedbackToUser(); + assertEquals("Invalid date format. Use d/DD/MM/YYYY.", message); + } + + @Test + void processCommand_noAmount_returnsIncorrectCommand() { + Command result = AddExpenseValidator.processCommand("add expense Lunch d/12/10/2024 c/FOOD"); + + // Check if result is an IncorrectCommand and compare the message + assertInstanceOf(IncorrectCommand.class, result); + String message = ((IncorrectCommand) result).getFeedbackToUser(); + assertEquals("Amount not entered.", message); + } + + @Test + void processCommand_negativeAmount_returnsIncorrectCommand() { + Command result = AddExpenseValidator.processCommand("add expense Lunch a/-50 d/12/10/2024 c/FOOD"); + + // Check if result is an IncorrectCommand and compare the message + assertInstanceOf(IncorrectCommand.class, result); + String message = ((IncorrectCommand) result).getFeedbackToUser(); + assertEquals("Amount should be a positive number with up to 2 decimal places.", message); + } + + + @Test + void processCommand_validCommand_returnsAddExpenseCommand() { + Command result = AddExpenseValidator.processCommand("add expense Lunch a/100 d/12/10/2024 c/FOOD"); + + // Check if the result is an AddExpenseCommand (a valid command) + assertInstanceOf(AddExpenseCommand.class, result); + AddExpenseCommand command = (AddExpenseCommand) result; + + // Verify the details of the created command + assertEquals("Lunch", command.getDescription()); + assertEquals(100, command.getAmount()); + assertEquals(LocalDate.of(2024, 10, 12), command.getDate()); + assertEquals(Category.FOOD, command.getCategory()); + } +} + diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidatorTest.java new file mode 100644 index 0000000000..7b355c5581 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/expense/DeleteExpenseValidatorTest.java @@ -0,0 +1,48 @@ +package seedu.budgetbuddy.validators.expense; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class DeleteExpenseValidatorTest { + + private String baseCommand; + + @BeforeEach + void setUp() { + baseCommand = "delete expense "; + } + + @Test + void testInvalidDeleteExpenseCommand() { + Command command = DeleteExpenseValidator.processCommand(baseCommand + "invalid"); + assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to invalid input."); + assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser()); + } + + @Test + void testNegativeIndex() { + Command command = DeleteExpenseValidator.processCommand(baseCommand + "-1"); + assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to negative index."); + assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser()); + } + + @Test + void testZeroIndex() { + Command command = DeleteExpenseValidator.processCommand(baseCommand + "0"); + assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to zero index."); + assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser()); + } + + @Test + void testEmptyInput() { + Command command = DeleteExpenseValidator.processCommand(baseCommand); + assertInstanceOf(IncorrectCommand.class, command, "Expected an IncorrectCommand due to missing index."); + assertEquals("Invalid Index", ((IncorrectCommand) command).getFeedbackToUser()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidatorTest.java new file mode 100644 index 0000000000..ccfef6d4d3 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayExpensesForMonthWithCategoriesValidatorTest.java @@ -0,0 +1,82 @@ +package seedu.budgetbuddy.validators.expense; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.DisplayExpensesForMonthWithCategoriesGraphCommand; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DisplayExpensesForMonthWithCategoriesValidatorTest { + + @Test + public void processCommand_validCommand_returnsDisplayExpensesCommand() { + // Given a valid command string + String command = "display expenses with categories m/10/2024"; + + // Process the command using the validator + Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command); + + // Check if the result is of type DisplayExpensesForMonthWithCategoriesGraphCommand + assertInstanceOf(DisplayExpensesForMonthWithCategoriesGraphCommand.class, result); + + // Check if the year and month were correctly parsed + YearMonth expectedYearMonth = YearMonth.of(2024, 10); + DisplayExpensesForMonthWithCategoriesGraphCommand displayCommand = + (DisplayExpensesForMonthWithCategoriesGraphCommand) result; + assertEquals(expectedYearMonth, displayCommand.getYearMonth(), "YearMonth should match the expected value."); + } + + @Test + public void processCommand_invalidDate_returnsIncorrectCommand() { + // Given a command with an invalid date format + String command = "display expenses with categories m/invalid-date"; + + // Process the command using the validator + Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command); + + // Check if the result is an IncorrectCommand + assertInstanceOf(IncorrectCommand.class, result); + + // Check if the error message is correct + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + assertEquals("Invalid Date", + incorrectCommand.getFeedbackToUser()); + } + + @Test + public void processCommand_emptyDate_returnsIncorrectCommand() { + // Given a command with no date provided + String command = "display expenses with categories"; + + // Process the command using the validator + Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command); + + // Check if the result is an IncorrectCommand + assertInstanceOf(IncorrectCommand.class, result); + + // Check if the error message is correct + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + assertEquals("Please provide a valid month", incorrectCommand.getFeedbackToUser()); + } + + @Test + public void processCommand_invalidMonthFormat_returnsIncorrectCommand() { + // Given a command with an invalid month format + String command = "display expenses with categories m/13/2024"; + + // Process the command using the validator + Command result = DisplayExpensesForMonthWithCategoriesValidator.processCommand(command); + + // Check if the result is an IncorrectCommand + assertInstanceOf(IncorrectCommand.class, result); + + // Check if the error message is correct + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + assertEquals("Invalid Date", incorrectCommand.getFeedbackToUser()); + } +} + diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidatorTest.java new file mode 100644 index 0000000000..4008d8cbe1 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/expense/DisplayTotalExpensesValidatorTest.java @@ -0,0 +1,51 @@ +package seedu.budgetbuddy.validators.expense; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.DisplayTotalExpensesCommand; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DisplayTotalExpensesValidatorTest { + + private String baseCommand; + + @BeforeEach + void setUp() { + baseCommand = "display monthly expenses"; + } + + @Test + void testValidYearInput() { + Command command = DisplayTotalExpensesValidator.processCommand(baseCommand + " y/2023"); + assertInstanceOf(DisplayTotalExpensesCommand.class, command); + assertEquals("Displaying expense graph for 2023", ((DisplayTotalExpensesCommand) command). + getFeedbackToUser()); + + } + + @Test + void testMissingYearInput() { + Command command = DisplayTotalExpensesValidator.processCommand(baseCommand); + assertInstanceOf(IncorrectCommand.class, command); + assertEquals("Please provide a year.", ((IncorrectCommand) command).getFeedbackToUser()); + } + + @Test + void testInvalidYearFormat() { + Command command = DisplayTotalExpensesValidator.processCommand(baseCommand + " y/abc"); + assertInstanceOf(IncorrectCommand.class, command); + assertEquals("Invalid or missing year format.", ((IncorrectCommand) command).getFeedbackToUser()); + } + + @Test + void testInvalidCommandFormat() { + Command command = DisplayTotalExpensesValidator.processCommand(baseCommand + " y2023"); + assertInstanceOf(IncorrectCommand.class, command); + assertEquals("Unknown command 'y2023'. Expected format: 'y/YYYY'", ((IncorrectCommand) command). + getFeedbackToUser()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/EditExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/EditExpenseValidatorTest.java new file mode 100644 index 0000000000..fa3dffa235 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/expense/EditExpenseValidatorTest.java @@ -0,0 +1,81 @@ +package seedu.budgetbuddy.validators.expense; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.EditExpenseCommand; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class EditExpenseValidatorTest { + + private ExpenseManager testExpenseManager; + + void createTestCase (){ + testExpenseManager = new ExpenseManager(); + Expense testExpense = new Expense("testExpense", 12.00, LocalDate.now(), Category.OTHERS); + testExpenseManager.addExpense(testExpense); + } + + @Test + void processFirstCommand_validIndexGiven_expectEditExpenseCommandType() { + createTestCase(); + String userInput = "edit expense 1"; + Command validCommand = EditExpenseValidator.processFirstCommand(userInput); + assertEquals(EditExpenseCommand.class, validCommand.getClass()); + } + + @Test + void processFirstCommand_invalidInputGiven_expectIncorrectCommandType() { + String userInput = "edit expense 50"; + Command invalidCommand = EditExpenseValidator.processFirstCommand(userInput); + assertEquals(IncorrectCommand.class ,invalidCommand.getClass()); + } + + @Test + void processSecondCommand_validAmountGiven_expectTrue() { + String userInput = "a/500"; + Boolean validResult = EditExpenseValidator.processSecondCommand(userInput); + assertEquals(true, validResult); + } + + @Test + void processSecondCommand_validCategoryGiven_expectTrue() { + String userInput = "c/food"; + Boolean validResult = EditExpenseValidator.processSecondCommand(userInput); + assertEquals(true, validResult); + } + + @Test + void processSecondCommand_validDateGiven_expectTrue() { + String userInput = "d/12/10/2024"; + Boolean validResult = EditExpenseValidator.processSecondCommand(userInput); + assertEquals(true, validResult); + } + + @Test + void processSecondCommand_invalidAmountGiven_expectFalse() { + String userInput = "a/-500"; + Boolean invalidResult = EditExpenseValidator.processSecondCommand(userInput); + assertEquals(false, invalidResult); + } + @Test + void processSecondCommand_invalidDateGiven_expectFalse() { + String userInput = "d/20202020"; + Boolean invalidResult = EditExpenseValidator.processSecondCommand(userInput); + assertEquals(false, invalidResult); + } + + @Test + void processSecondCommand_invalidCategoryGiven_expectFalse() { + String userInput = "c/invalid"; + Boolean invalidResult = EditExpenseValidator.processSecondCommand(userInput); + assertEquals(false, invalidResult); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/expense/ListExpenseValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/expense/ListExpenseValidatorTest.java new file mode 100644 index 0000000000..0173c3f740 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/expense/ListExpenseValidatorTest.java @@ -0,0 +1,80 @@ +package seedu.budgetbuddy.validators.expense; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.expense.ListExpenseCommand; +import seedu.budgetbuddy.transaction.Category; + +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.budgetbuddy.validators.DateValidator.validateYearMonth; + +class DisplayExpenseValidatorTest { + + @Test + void checkListType_categoryIsEmpty_expectNullCategory(){ + ListExpenseValidator expenseValidator = new ListExpenseValidator(); + Category category = null; + YearMonth yearMonth = validateYearMonth("02/2024"); + ListExpenseCommand command = (ListExpenseCommand) expenseValidator.checkListType(category, yearMonth); + assertEquals(null, command.getCategory()); + assertEquals(yearMonth,command.getMonth()); + } + + @Test + void checkListType_monthIsEmpty_expectNullMonth(){ + ListExpenseValidator expenseValidator = new ListExpenseValidator(); + Category category = Category.FOOD; + YearMonth yearMonth = null; + ListExpenseCommand command = (ListExpenseCommand) expenseValidator.checkListType(category, yearMonth); + assertEquals(category, command.getCategory()); + assertEquals(yearMonth,command.getMonth()); + } + + @Test + void checkListType_categoryMonthSpecified_expectSameValues(){ + ListExpenseValidator expenseValidator = new ListExpenseValidator(); + Category category = Category.FOOD; + YearMonth yearMonth = validateYearMonth("02/2024"); + ListExpenseCommand command = (ListExpenseCommand) expenseValidator.checkListType(category, yearMonth); + assertEquals(category, command.getCategory()); + assertEquals(yearMonth,command.getMonth()); + } + + @Test + void processCommand_validInput_expectListExpenseCommandType(){ + String userInput = "list expenses"; + Command validCommand = ListExpenseValidator.processCommand(userInput); + assertEquals(validCommand.getClass(), ListExpenseCommand.class); + } + + @Test + void processCommand_validInputWithMonth_expectListExpenseCommandType(){ + String userInput = "list expenses m/12/2024"; + Command validCommand = ListExpenseValidator.processCommand(userInput); + assertEquals(validCommand.getClass(), ListExpenseCommand.class); + } + + @Test + void processCommand_validInputWithCategory_expectListExpenseCommandType(){ + String userInput = "list expenses c/food"; + Command validCommand = ListExpenseValidator.processCommand(userInput); + assertEquals(validCommand.getClass(), ListExpenseCommand.class); + } + + @Test + void processCommand_validInputWithMonthAndCategory_expectListExpenseCommandType(){ + String userInput = "list expenses m/12/2024 c/food"; + Command validCommand = ListExpenseValidator.processCommand(userInput); + assertEquals(validCommand.getClass(), ListExpenseCommand.class); + } + + @Test + void processCommand_invalidInput_expectInvalidCommandType(){ + String userInput = "list expenses c/invalid"; + Command invalidCommand = ListExpenseValidator.processCommand(userInput); + assertEquals(invalidCommand.getClass(), IncorrectCommand.class); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/income/AddIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/AddIncomeValidatorTest.java new file mode 100644 index 0000000000..9ce492150c --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/income/AddIncomeValidatorTest.java @@ -0,0 +1,97 @@ +package seedu.budgetbuddy.validators.income; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.income.AddIncomeCommand; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class AddIncomeValidatorTest { + + @Test + void testNoDescriptionProvided() { + Command result = AddIncomeValidator.processCommand("add income"); + assertInstanceOf(IncorrectCommand.class, result); + assertEquals("No description provided.", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void testValidCommand() { + String command = "add income Lunch income a/50.5 d/25/10/2024"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(AddIncomeCommand.class, result, "Expected AddIncomeCommand instance"); + AddIncomeCommand addIncomeCommand = (AddIncomeCommand) result; + + assertEquals("Lunch income", addIncomeCommand.getDescription(), "Expected description to " + + "match"); + assertEquals(50.5, addIncomeCommand.getAmount(), "Expected amount to match"); + assertEquals(LocalDate.of(2024, 10, 25), addIncomeCommand.getDate(), + "Expected date to match"); + } + + @Test + void testMissingAmount() { + String command = "add income Salary d/25/10/2024"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(IncorrectCommand.class, result); + assertEquals("Amount not entered.", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void testInvalidAmountFormat() { + String command = "add income Bonus a/abc d/25/10/2024"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(IncorrectCommand.class, result); + assertEquals("Amount should be a positive number with up to 2 decimal places.", + ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void testMissingDate() { + String command = "add income Freelance job a/100"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(AddIncomeCommand.class, result); + AddIncomeCommand addIncomeCommand = (AddIncomeCommand) result; + + assertEquals("Freelance job", addIncomeCommand.getDescription()); + assertEquals(100, addIncomeCommand.getAmount()); + assertEquals(LocalDate.now(), addIncomeCommand.getDate(), "Expected default date to be today"); + } + + @Test + void testInvalidDateFormat() { + String command = "add income Part-time a/50 d/invalid-date"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(IncorrectCommand.class, result); + assertEquals("Invalid date format. Use d/dd/MM/yyyy.", ((IncorrectCommand) result). + getFeedbackToUser()); + } + + @Test + void testEmptyDescription() { + String command = "add income a/100 d/25/10/2024"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(IncorrectCommand.class, result); + assertEquals("Description cannot be empty.", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void testNegativeAmount() { + String command = "add income Refund a/-50 d/25/10/2024"; + Command result = AddIncomeValidator.processCommand(command); + + assertInstanceOf(IncorrectCommand.class, result); + assertEquals("Amount should be a positive number with up to 2 decimal places.", + ((IncorrectCommand) result).getFeedbackToUser()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidatorTest.java new file mode 100644 index 0000000000..a05c78ef27 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/income/DeleteIncomeValidatorTest.java @@ -0,0 +1,78 @@ +package seedu.budgetbuddy.validators.income; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.income.DeleteIncomeCommand; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DeleteIncomeValidatorTest { + + @Test + void processCommand_outOfRangeIndex_returnsIncorrectIncomeCommand() { + String command = "delete income 10"; + Command result = DeleteIncomeValidator.processCommand(command.trim()); + + assertTrue(result instanceof IncorrectCommand); + assertEquals("Invalid Index", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void processCommand_noIndexProvided_returnsIncorrectCommandIndexNotGiven() { + String command = "delete income"; + Command result = DeleteIncomeValidator.processCommand(command); + + assertTrue(result instanceof IncorrectCommand); + assertEquals("Index not given", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void processCommand_invalidIndex_returnsIncorrectCommandInvalidIndex() { + String command = "delete income abc"; + Command result = DeleteIncomeValidator.processCommand(command); + + assertTrue(result instanceof IncorrectCommand); + assertEquals("Invalid Index", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void processCommand_validIndex_returnsDeleteIncomeCommand() { + IncomeManager.addIncome(new Income("Salary", 5000.00, LocalDate.of(2024, 11, 1))); + IncomeManager.addIncome(new Income("Freelance", 1500.00, LocalDate.of(2024, 11, 5))); + IncomeManager.addIncome(new Income("Bonus", 2000.00, LocalDate.of(2024, 11, 10))); + + String command = "delete income 3"; + Command result = DeleteIncomeValidator.processCommand(command); + + assertTrue(result instanceof DeleteIncomeCommand); + assertEquals(2, ((DeleteIncomeCommand) result).getIndex()); // Expecting index to be 2 due to 0-based indexing + } + + @Test + void processCommand_negativeIndex_returnsIncorrectCommandInvalidIndex() { + String command = "delete income -1"; + Command result = DeleteIncomeValidator.processCommand(command); + + assertTrue(result instanceof IncorrectCommand); + assertEquals("Invalid Index", ((IncorrectCommand) result).getFeedbackToUser()); + } + + @Test + void processCommand_extraSpacesAfterIndex_returnsDeleteIncomeCommand() { + IncomeManager.addIncome(new Income("Salary", 5000.00, LocalDate.of(2024, 11, 1))); + IncomeManager.addIncome(new Income("Freelance", 1500.00, LocalDate.of(2024, 11, 5))); + IncomeManager.addIncome(new Income("Bonus", 2000.00, LocalDate.of(2024, 11, 10))); + + String command = "delete income 3 "; + Command result = DeleteIncomeValidator.processCommand(command.trim()); + + assertTrue(result instanceof DeleteIncomeCommand); + assertEquals(2, ((DeleteIncomeCommand) result).getIndex()); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidatorTest.java new file mode 100644 index 0000000000..2a4c62f172 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/income/DisplayIncomeSpentValidatorTest.java @@ -0,0 +1,92 @@ +package seedu.budgetbuddy.validators.income; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.income.DisplayIncomeSpentCommand; +import seedu.budgetbuddy.exceptions.BudgetBuddyException; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.time.LocalDate; +import java.time.YearMonth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DisplayIncomeSpentValidatorTest { + + private static final YearMonth VALID_MONTH = YearMonth.of(2024, 11); + private static final YearMonth INVALID_MONTH = YearMonth.of(2023, 2); + + @BeforeEach + public void setUp() { + // Reset IncomeManager to ensure no residual data from other tests + IncomeManager.getIncomes().clear(); + } + + @Test + public void processCommand_validCommandWithoutMonth_returnsCommandForCurrentMonth() throws BudgetBuddyException { + // Add mock income for current month + IncomeManager.addIncome(new Income("Salary", 2000.0, + LocalDate.of(2024, 11, 5))); + + // Call method with valid command without month specification + Command command = DisplayIncomeSpentValidator.processCommand("display income spent"); + + assertNotNull(command); + assertTrue(command instanceof DisplayIncomeSpentCommand); + DisplayIncomeSpentCommand displayCommand = (DisplayIncomeSpentCommand) command; + assertEquals(VALID_MONTH, displayCommand.getMonth()); // Should match current month (November 2024) + } + + @Test + public void processCommand_validCommandWithValidMonth_returnsCommandForSpecifiedMonth() + throws BudgetBuddyException { + // Add dummy income for the specified month (November 2024) + IncomeManager.addIncome(new Income("Salary", 2000.0, + LocalDate.of(2024, 11, 5))); + + // Call method with valid command specifying a month + Command command = DisplayIncomeSpentValidator.processCommand("display income spent m/11/2024"); + + assertNotNull(command); + assertTrue(command instanceof DisplayIncomeSpentCommand); + DisplayIncomeSpentCommand displayCommand = (DisplayIncomeSpentCommand) command; + assertEquals(VALID_MONTH, displayCommand.getMonth()); // Should match November 2024 + } + + @Test + public void processCommand_invalidMonthFormat_throwsBudgetBuddyException() { + // Call method with an invalid month format + BudgetBuddyException thrown = assertThrows(BudgetBuddyException.class, () -> { + DisplayIncomeSpentValidator.processCommand("display income spent m/invalid-month"); + }); + assertTrue(thrown.getMessage().contains("Invalid month format. Use m/MM/yyyy.")); + } + + @Test + public void processCommand_noIncomeRecordedForMonth_throwsBudgetBuddyException() { + // Call method with valid command specifying a month but no income added for that month + BudgetBuddyException thrown = assertThrows(BudgetBuddyException.class, () -> { + DisplayIncomeSpentValidator.processCommand("display income spent m/11/2024"); + }); + assertTrue(thrown.getMessage().contains("No income recorded for the month: 2024-11")); + } + + @Test + public void processCommand_validMonthNoIncome_throwsBudgetBuddyException() { + // Add dummy income with a value of 0 for the month of November + IncomeManager.addIncome(new Income("Salary", 0.0, + LocalDate.of(2024, 11, 5))); + + // Call method with valid month and no income recorded for that month + BudgetBuddyException thrown = assertThrows(BudgetBuddyException.class, () -> { + DisplayIncomeSpentValidator.processCommand("display income spent m/11/2024"); + }); + assertTrue(thrown.getMessage().contains("No income recorded for the month: 2024-11")); + } +} + diff --git a/src/test/java/seedu/budgetbuddy/validators/income/EditIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/EditIncomeValidatorTest.java new file mode 100644 index 0000000000..568b9c1498 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/income/EditIncomeValidatorTest.java @@ -0,0 +1,74 @@ +package seedu.budgetbuddy.validators.income; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.income.EditIncomeCommand; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class EditIncomeValidatorTest { + + private IncomeManager testIncomeManager; + + void createTestCase (){ + testIncomeManager = new IncomeManager(); + Income testIncome = new Income("testExpense", 12.00, LocalDate.now()); + testIncomeManager.addIncome(testIncome); + } + + @Test + void processFirstCommand_validIndexGiven_expectEditExpenseCommandType() { + createTestCase(); + String userInput = "edit income 1"; + Command validCommand = EditIncomeValidator.processFirstCommand(userInput); + assertEquals(EditIncomeCommand.class, validCommand.getClass()); + } + + @Test + void processFirstCommand_invalidInputGiven_expectIncorrectCommandType() { + String userInput = "edit income 50"; + Command invalidCommand = EditIncomeValidator.processFirstCommand(userInput); + assertEquals(IncorrectCommand.class ,invalidCommand.getClass()); + } + + @Test + void processSecondCommand_validAmountGiven_expectTrue() { + String userInput = "a/500"; + Boolean validResult = EditIncomeValidator.processSecondCommand(userInput); + assertEquals(true, validResult); + } + + @Test + void processSecondCommand_validDateGiven_expectTrue() { + String userInput = "d/12/10/2024"; + Boolean validResult = EditIncomeValidator.processSecondCommand(userInput); + assertEquals(true, validResult); + } + + @Test + void processSecondCommand_emptyInputGiven_expectFalse() { + String userInput = ""; + Boolean invalidResult = EditIncomeValidator.processSecondCommand(userInput); + assertEquals(false, invalidResult); + } + + @Test + void processSecondCommand_invalidAmountGiven_expectFalse() { + String userInput = "a/-500"; + Boolean invalidResult = EditIncomeValidator.processSecondCommand(userInput); + assertEquals(false, invalidResult); + } + + @Test + void processSecondCommand_invalidDateGiven_expectFalse() { + String userInput = "d/202020202"; + Boolean invalidResult = EditIncomeValidator.processSecondCommand(userInput); + assertEquals(false, invalidResult); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/income/ListIncomeValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/income/ListIncomeValidatorTest.java new file mode 100644 index 0000000000..df7047054d --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/income/ListIncomeValidatorTest.java @@ -0,0 +1,33 @@ +package seedu.budgetbuddy.validators.income; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.commands.Command; +import seedu.budgetbuddy.commands.IncorrectCommand; +import seedu.budgetbuddy.commands.income.ListIncomeCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class ListIncomeValidatorTest { + + @Test + void processCommand_validInput_expectListExpenseCommandType(){ + String userInput = "list incomes"; + Command validCommand = ListIncomeValidator.processCommand(userInput); + assertEquals(validCommand.getClass(), ListIncomeCommand.class); + } + + @Test + void processCommand_validInputWithMont_expectListExpenseCommandType(){ + String userInput = "list incomes m/12/2024"; + Command validCommand = ListIncomeValidator.processCommand(userInput); + assertEquals(validCommand.getClass(), ListIncomeCommand.class); + } + + @Test + void processCommand_invalidInput_expectInvalidCommandType(){ + String userInput = "list incomes m/20/2024"; + Command invalidCommand = ListIncomeValidator.processCommand(userInput); + assertEquals(invalidCommand.getClass(), IncorrectCommand.class); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/saving/AmountValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/saving/AmountValidatorTest.java new file mode 100644 index 0000000000..9c652794d5 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/saving/AmountValidatorTest.java @@ -0,0 +1,50 @@ +package seedu.budgetbuddy.validators.saving; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.validators.AmountValidator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class AmountValidatorTest { + + @Test + void validateAmount_inputIsDouble_expectDouble() { + AmountValidator amountValidator = new AmountValidator(); + String input = "a/123.45"; + Double result = amountValidator.validateAmount(input); + assertEquals(Double.valueOf(123.45), result); + } + + @Test + void validateAmount_inputWith3DecimalPoints_expectNegativeOne() { + AmountValidator amountValidator = new AmountValidator(); + String input = "a/123.4567"; + Double result = amountValidator.validateAmount(input); + assertEquals(Double.valueOf(-1), result); + } + + @Test + void validateAmount_inputIsNotDouble_expectNegativeOne() { + AmountValidator amountValidator = new AmountValidator(); + String input = "a/invalid"; + Double result = amountValidator.validateAmount(input); + assertEquals(Double.valueOf(-1), result); + } + + @Test + void validateAmount_inputContainsAlphabet_expectNegativeOne() { + AmountValidator amountValidator = new AmountValidator(); + String input = "a/123d"; + Double result = amountValidator.validateAmount(input); + assertEquals(Double.valueOf(-1), result); + } + + @Test + void validateAmount_inputContainsTwoDecimal_expectNegativeOne() { + AmountValidator amountValidator = new AmountValidator(); + String input = "a/123.1.1"; + Double result = amountValidator.validateAmount(input); + assertEquals(Double.valueOf(-1), result); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/saving/DateValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/saving/DateValidatorTest.java new file mode 100644 index 0000000000..e8ca40fcb6 --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/saving/DateValidatorTest.java @@ -0,0 +1,27 @@ +package seedu.budgetbuddy.validators.saving; + +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.validators.DateValidator; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + + +class DateValidatorTest { + @Test + void validateDate_validDate_expectParsedDate() { + String input = "d/15/8/2023"; + LocalDate result = DateValidator.validateDate(input); + LocalDate expected = LocalDate.of(2023, 8, 15); + assertEquals(expected, result); + } + + @Test + void validateDate_invalidDate_expectNull() { + String input = "d/invalid"; + LocalDate result = DateValidator.validateDate(input); + assertNull(result); + } +} diff --git a/src/test/java/seedu/budgetbuddy/validators/saving/IndexValidatorTest.java b/src/test/java/seedu/budgetbuddy/validators/saving/IndexValidatorTest.java new file mode 100644 index 0000000000..0ac417c89b --- /dev/null +++ b/src/test/java/seedu/budgetbuddy/validators/saving/IndexValidatorTest.java @@ -0,0 +1,82 @@ +package seedu.budgetbuddy.validators.saving; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.budgetbuddy.transaction.expense.Expense; +import seedu.budgetbuddy.transaction.expense.ExpenseManager; +import seedu.budgetbuddy.transaction.income.Income; +import seedu.budgetbuddy.transaction.income.IncomeManager; +import seedu.budgetbuddy.transaction.Category; +import seedu.budgetbuddy.validators.IndexValidator; + +import java.time.LocalDate; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/** + * JUnit test class for IndexValidator. + */ +class IndexValidatorTest { + + @BeforeEach + void setUp() { + // Initialize a list of expenses with Category, description, amount, and date + ArrayList expenses = new ArrayList<>(); + expenses.add(new Expense("Test1", 10.50, LocalDate.of(2024, 1, 1), Category.OTHERS)); + expenses.add(new Expense("Test2", 2.80, LocalDate.of(2024, 2, 1), Category.OTHERS)); + expenses.add(new Expense("Test3", 12.00, LocalDate.of(2024, 3, 1), Category.OTHERS)); + + // Initialize a list of incomes + ArrayList incomes = new ArrayList<>(); + incomes.add(new Income("Test1", 10.50, LocalDate.of(2024, 1, 1))); + incomes.add(new Income("Test2", 1.50, LocalDate.of(2024, 2, 1))); + + // Initialize ExpenseManager and IncomeManager with the number of expenses and incomes + new ExpenseManager(expenses, expenses.size()); + new IncomeManager(incomes, incomes.size()); + } + + @Test + void validateExpenseIndex_validIndexWithinRange_expectValidIndex() { + String input = "2"; + int result = IndexValidator.validateExpenseIndex(input); + assertEquals(2, result, "Expected valid index 2 but got " + result); + } + + @Test + void validateExpenseIndex_invalidIndex_expectNegativeOne() { + String input = "invalid"; + int result = IndexValidator.validateExpenseIndex(input); + assertEquals(-1, result, "Expected -1 for invalid input but got " + result); + } + + @Test + void validateExpenseIndex_indexOutOfRange_expectNegativeOne() { + String input = "5"; // Out of range since there are only 3 expenses + int result = IndexValidator.validateExpenseIndex(input); + assertEquals(-1, result, "Expected -1 for out-of-range index but got " + result); + } + + @Test + void validateIncomeIndex_validIndexWithinRange_expectValidIndex() { + String input = "1"; + int result = IndexValidator.validateIncomeIndex(input); + assertEquals(1, result, "Expected valid index 1 but got " + result); + } + + @Test + void validateIncomeIndex_invalidIndex_expectNegativeOne() { + String input = "invalid"; + int result = IndexValidator.validateIncomeIndex(input); + assertEquals(-1, result, "Expected -1 for invalid input but got " + result); + } + + @Test + void validateIncomeIndex_indexOutOfRange_expectNegativeOne() { + String input = "3"; // Out of range since there are only 2 incomes + int result = IndexValidator.validateIncomeIndex(input); + assertEquals(-1, result, "Expected -1 for out-of-range index but got " + result); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..83d0e38c4f 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,339 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +======================================================== +Welcome to Budget Buddy! +======================================================== +Enter commands: ======================================================== +No description provided. +======================================================== +Enter commands: ======================================================== +Amount not entered. +======================================================== +Enter commands: ======================================================== +Amount should be a positive number with up to 2 decimal places. +======================================================== +Enter commands: ======================================================== +Invalid date format. Use d/DD/MM/YYYY. +======================================================== +Enter commands: ======================================================== +The following expense transaction has been added: +Description: Japan Flight Amount: 123.45 Date: 2024-12-30 Category: TRANSPORT +You have 1 expense transaction(s) in total. +The remaining budget for 2024-12 in the TRANSPORT category is: -123.45 +Caution! You have exceeded your budget! +======================================================== +Enter commands: ======================================================== +The following expense transaction has been added: +Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS +You have 2 expense transaction(s) in total. +The remaining budget for 2024-10 in the OTHERS category is: -100.10 +Caution! You have exceeded your budget! +======================================================== +Enter commands: ======================================================== +1. Description: Japan Flight Amount: 123.45 Date: 2024-12-30 Category: TRANSPORT +2. Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS +There are 2 expense(s) in total, with a sum of $223.55. +======================================================== +Enter commands: ======================================================== +The following expense transaction has been deleted: +Description: Japan Flight Amount: 123.45 Date: 2024-12-30 Category: TRANSPORT +You have 1 expense transaction(s) in total. +No budget found for 2024-12. +======================================================== +Enter commands: ======================================================== +Invalid input +======================================================== +Enter commands: ======================================================== +1. Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS +There are 1 expense(s) in total, with a sum of $100.10. +======================================================== +Enter commands: ======================================================== +No description provided. +======================================================== +Enter commands: ======================================================== +Description cannot be empty. +======================================================== +Enter commands: ======================================================== +No description provided. +======================================================== +Enter commands: ======================================================== +Description cannot be empty. +======================================================== +Enter commands: ======================================================== +Amount should be a positive number with up to 2 decimal places. +======================================================== +Enter commands: ======================================================== +Invalid date format. Use d/dd/MM/yyyy. +======================================================== +Enter commands: ======================================================== +The following income transaction has been added: +Description: Tuition Amount: 100.00 Date: 2024-12-15 +You have 1 income transaction(s) in total. +======================================================== +Enter commands: ======================================================== +The following income transaction has been added: +Description: Internship Amount: 123.00 Date: 2024-10-13 +You have 2 income transaction(s) in total. +======================================================== +Enter commands: ======================================================== +No amount provided. +======================================================== +Enter commands: ======================================================== +Invalid amount: -1000.0. Amount must be a positive value. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 1234.00 Date: 2024-11 + Category: {OTHERS=1234.00} +You have 1 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 800.00 Date: 2024-03 + Category: {OTHERS=800.00} +You have 2 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 3534.00 Date: 2024-11 + Category: {OTHERS=3534.00} +You have 2 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 3000.00 Date: 2024-07 + Category: {OTHERS=3000.00} +You have 3 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 5000.00 Date: 2024-02 + Category: {OTHERS=5000.00} +You have 4 budget(s) in total. +======================================================== +Enter commands: ======================================================== +No amount provided. +======================================================== +Enter commands: ======================================================== +Invalid amount: -1000.0. Must be a positive value. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 2300.00 Date: 2024-11 + Category: {OTHERS=2300.00} +You have 4 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget has been deleted as its amount reached zero: +Date: 2024-03 +You have 3 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget has been deleted as its amount reached zero: +Date: 2024-11 +You have 2 budget(s) in total. +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 3500.00 Date: 2024-02 + Category: {OTHERS=3500.00} +You have 2 budget(s) in total. +======================================================== +Enter commands: ======================================================== +Here is the budget for the specified month: +Total Monthly Budget: 3500.00 Date: 2024-02 + Category: {OTHERS=3500.00} +======================================================== +Enter commands: ======================================================== +No budget found for date: 2022-07 +======================================================== +Enter commands: ======================================================== +Listing up to the 12 most recent budgets: +1. Total Monthly Budget: 3000.00 Date: 2024-07 + Category: {OTHERS=3000.00} +2. Total Monthly Budget: 3500.00 Date: 2024-02 + Category: {OTHERS=3500.00} +======================================================== +Enter commands: ======================================================== +Invalid input +======================================================== +Enter commands: ======================================================== +1. Description: Tuition Amount: 100.00 Date: 2024-12-15 +2. Description: Internship Amount: 123.00 Date: 2024-10-13 +There are 2 income(s) in total, with a sum of $223.00. +======================================================== +Enter commands: ======================================================== +Invalid input +======================================================== +Enter commands: ======================================================== +Invalid input +======================================================== +Enter commands: ======================================================== +Invalid input +======================================================== +Enter commands: ======================================================== +The following income transaction has been deleted: +Description: Tuition Amount: 100.00 Date: 2024-12-15 +You have 1 income transaction(s) in total. +======================================================== +Enter commands: ======================================================== +1. Description: Internship Amount: 123.00 Date: 2024-10-13 +There are 1 income(s) in total, with a sum of $123.00. +======================================================== +Enter commands: ======================================================== +The following expense transaction has been added: +Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT +You have 2 expense transaction(s) in total. +The remaining budget for 2024-10 in the TRANSPORT category is: -123.45 +Caution! You have exceeded your budget! +======================================================== +Enter commands: ======================================================== +The following expense transaction has been added: +Description: Ipad Amount: 123.45 Date: 2024-08-30 Category: EDUCATION +You have 3 expense transaction(s) in total. +The remaining budget for 2024-08 in the EDUCATION category is: -123.45 +Caution! You have exceeded your budget! +======================================================== +Enter commands: ======================================================== +1. Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT +Your total expense(s) for TRANSPORT are $123.45 +======================================================== +Enter commands: ======================================================== +1. Description: Ipad Amount: 123.45 Date: 2024-08-30 Category: EDUCATION +Your total expense(s) for August 2024 are $123.45 +======================================================== +Enter commands: ======================================================== +1. Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT +Your total expense(s) for TRANSPORT in October 2024 are $123.45 +======================================================== +Enter commands: ======================================================== +Unknown category. Use a valid category. +======================================================== +Enter commands: ======================================================== +Invalid month format. Use m/MM/yyyy. +======================================================== +Enter commands: ======================================================== +No expense entry with given parameters found, try again with a different parameter. +======================================================== +Enter commands: ======================================================== +No expense entry with given parameters found, try again with a different parameter. +======================================================== +Enter commands: ======================================================== +The following income transaction has been added: +Description: Tuition Amount: 100.00 Date: 2024-12-15 +You have 2 income transaction(s) in total. +======================================================== +Enter commands: ======================================================== +The following income transaction has been added: +Description: Internship Amount: 123.00 Date: 2024-10-13 +You have 3 income transaction(s) in total. +======================================================== +Enter commands: ======================================================== +1. Description: Internship Amount: 123.00 Date: 2024-10-13 +2. Description: Tuition Amount: 100.00 Date: 2024-12-15 +3. Description: Internship Amount: 123.00 Date: 2024-10-13 +There are 3 income(s) in total, with a sum of $346.00. +======================================================== +Enter commands: ======================================================== +1. Description: Internship Amount: 123.00 Date: 2024-10-13 +2. Description: Internship Amount: 123.00 Date: 2024-10-13 +There are 2 income(s) in total for October 2024, with a sum of $246.0. +======================================================== +Enter commands: ======================================================== +No income entry with given month found, try again with a different month. +======================================================== +Enter commands: ======================================================== +Invalid month format. Use m/MM/yyyy. +======================================================== +Enter commands: ======================================================== +The following income transaction has been added: +Description: tutor Amount: 1500.00 Date: 2023-10-20 +You have 4 income transaction(s) in total. +======================================================== +Enter commands: ======================================================== +The following expense transaction has been added: +Description: food Amount: 550.00 Date: 2023-10-30 Category: OTHERS +You have 4 expense transaction(s) in total. +The remaining budget for 2023-10 in the OTHERS category is: -550.00 +Caution! You have exceeded your budget! +======================================================== +Enter commands: ======================================================== +Percentage of income spent for 2023-10: 36.7% +======================================================== +Enter commands: ======================================================== +1. Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS +2. Description: Japan Flight Amount: 123.45 Date: 2024-10-30 Category: TRANSPORT +3. Description: Ipad Amount: 123.45 Date: 2024-08-30 Category: EDUCATION +4. Description: food Amount: 550.00 Date: 2023-10-30 Category: OTHERS +There are 4 expense(s) in total, with a sum of $897.00. +======================================================== +Enter commands: Edit the following fields as follows: Amount: a/, Category: c/, Date: d/ +You may the exit change menu by pressing enter with no input. +Currently Editing Entry: +Description: Buy Tesla Stock Amount: 100.10 Date: 2024-10-02 Category: OTHERS +Enter edit Field: ======================================================== +Edited Expense: +Description: Buy Tesla Stock Amount: 50.00 Date: 2024-10-02 Category: OTHERS +======================================================== +Enter commands: ======================================================== +The following expense transaction has been added: +Description: Fridge Amount: 5000.00 Date: 2024-02-01 Category: OTHERS +You have 5 expense transaction(s) in total. +The remaining budget for 2024-02 in the OTHERS category is: -1500.00 +Caution! You have exceeded your budget! +======================================================== +Enter commands: ======================================================== +The following budget amount has been updated: +Total Monthly Budget: 5500.00 Date: 2024-02 + Category: {OTHERS=5500.00} +You have 2 budget(s) in total. +======================================================== +Enter commands: ======================================================== +All budgets after expense deductions: +Total Monthly Budget: 3000.00 Date: 2024-07 + Category: {OTHERS=3000.00} +Total Monthly Budget: 500.00 Date: 2024-02 + Category: {OTHERS=500.00} +Total Monthly Budget: -173.45 Date: 2024-10 + Category: {TRANSPORT=-123.45, OTHERS=-50.00} +Total Monthly Budget: -123.45 Date: 2024-08 + Category: {EDUCATION=-123.45} +Total Monthly Budget: -550.00 Date: 2023-10 + Category: {OTHERS=-550.00} +======================================================== +Enter commands: ======================================================== +1. Description: Internship Amount: 123.00 Date: 2024-10-13 +2. Description: Tuition Amount: 100.00 Date: 2024-12-15 +3. Description: Internship Amount: 123.00 Date: 2024-10-13 +4. Description: tutor Amount: 1500.00 Date: 2023-10-20 +There are 4 income(s) in total, with a sum of $1846.00. +======================================================== +Enter commands: Edit the following fields as follows: Amount: a/, Date: d/ +You may the exit change menu by pressing enter with no input. +Currently Editing Entry: +Description: Internship Amount: 123.00 Date: 2024-10-13 +Enter edit Field: ======================================================== +Edited Income: +Description: Internship Amount: 10000.00 Date: 2024-12-10 +======================================================== +Enter commands: ======================================================== +Edit index must be greater than 0. +======================================================== +Enter commands: ======================================================== +Input index is larger than the number of incomes. Try with a smaller index +======================================================== +Enter commands: Edit the following fields as follows: Amount: a/, Date: d/ +You may the exit change menu by pressing enter with no input. +Currently Editing Entry: +Description: Internship Amount: 123.00 Date: 2024-10-13 +Enter edit Field: ======================================================== +No valid fields detected, Exiting change menu. +======================================================== +Enter commands: ======================================================== +1. Description: Internship Amount: 10000.00 Date: 2024-12-10 +2. Description: Tuition Amount: 100.00 Date: 2024-12-15 +3. Description: Internship Amount: 123.00 Date: 2024-10-13 +4. Description: tutor Amount: 1500.00 Date: 2023-10-20 +There are 4 income(s) in total, with a sum of $11723.00. +======================================================== +Enter commands: ======================================================== +Bye! +======================================================== diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..9d462f8f0e 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,74 @@ -James Gosling \ No newline at end of file +add expense +add expense no amount +add expense negative amount a/-1 +add expense wrong date format a/123.45 d/wrong +add expense Japan Flight a/123.45 d/30/12/2024 c/transport +add expense Buy Tesla Stock a/100.10 d/02/10/2024 +list expenses +delete expense 1 +list expense +list expenses +add expense +add expense a/122 +add income +add income a/123 +add income Negative amount a/-1 +add income wrong date a/122 d/wrong +add income Tuition a/100.00 d/15/12/2024 +add income Internship a/123 d/13/10/2024 +add budget +add budget a/-1000 +add budget a/1234 m/11/2024 +add budget a/800 m/03/2024 +add budget a/2300 m/11/2024 +add budget a/3000 m/07/2024 +add budget a/5000 m/02/2024 +deduct budget +deduct budget a/-1000 +deduct budget a/1234 m/11/2024 +deduct budget a/800 m/03/2024 +deduct budget a/2300 m/11/2024 +deduct budget a/1500 m/02/2024 +list budgets m/02/2024 +list budgets m/07/2022 +list budgets +list budget +list incomes +remove income 1 +remove incomes 1 +list income +delete income 1 +list incomes +add expense Japan Flight a/123.45 d/30/10/2024 c/transport +add expense Ipad a/123.45 d/30/08/2024 c/education +list expenses c/transport +list expenses m/08/2024 +list expenses c/transport m/10/2024 +list expenses c/incorrect category +list expenses m/invalid month +list expenses c/food +list expenses c/education m/02/2024 +add income Tuition a/100.00 d/15/12/2024 +add income Internship a/123 d/13/10/2024 +list incomes +list incomes m/10/2024 +list incomes m/08/2024 +list incomes m/20/2024 +add income tutor a/1500 d/20/10/2023 +add expense food a/550 d/30/10/2023 +display income spent m/10/2023 +list expenses +edit expense 1 +a/50 +add expense Fridge a/5000 d/1/2/2024 c/others +add budget a/2000 m/02/2024 +list remaining budget +list incomes +edit income 1 +a/10000 d/10/12/2024 +edit income -10 +edit income 50 +edit income 3 +c/food +list incomes +bye \ No newline at end of file