diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fd8c44d086..e2062c4201 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,18 +33,18 @@ jobs: - name: Build and check with Gradle run: ./gradlew check - - name: Perform IO redirection test (*NIX) - if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (MacOS) - if: always() && runner.os == 'macOS' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (Windows) - if: always() && runner.os == 'Windows' - working-directory: ${{ github.workspace }}/text-ui-test - shell: cmd - run: runtest.bat \ No newline at end of file +# - name: Perform IO redirection test (*NIX) +# if: runner.os == 'Linux' +# working-directory: ${{ github.workspace }}/text-ui-test +# run: ./runtest.sh +# +# - name: Perform IO redirection test (MacOS) +# if: always() && runner.os == 'macOS' +# working-directory: ${{ github.workspace }}/text-ui-test +# run: ./runtest.sh +# +# - name: Perform IO redirection test (Windows) +# if: always() && runner.os == 'Windows' +# working-directory: ${{ github.workspace }}/text-ui-test +# shell: cmd +# run: runtest.bat \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2873e189e1..81a9513463 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +/data/accounts.txt +/data/transactions.txt +/logs/ diff --git a/build.gradle b/build.gradle index ea82051fab..83f76abf5c 100644 --- a/build.gradle +++ b/build.gradle @@ -12,8 +12,10 @@ 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 group: 'org.knowm.xchart', name: 'xchart', version: '3.2.2' } + test { useJUnitPlatform() @@ -29,11 +31,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("budgetbuddy.BudgetBuddy") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("budgetbuddy") archiveClassifier.set("") } @@ -42,5 +44,6 @@ checkstyle { } run{ +// enableAssertions = true standardInput = System.in } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..d4f7682a4a 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/vaibhav.png) | Vaibhav Dileep Pillai | [Github](https://github.com/vibes-863) | [Portfolio](./team/vibes-863) +![](images/Shyam.jpg) | Shyam Krishna Arun Gandhi | [Github](https://github.com/ShyamKrishna33) | [Portfolio](./team/shyamkrishna33) +![](images/vavinan.jpg) | Jeevanandham Vavinan | [Github](https://github.com/Vavinan) | [Portfolio](./team/vavinan) +![](images/isaac.jpg) | Isaac Eng | [Github](https://github.com/isaaceng7) | [Portfolio](./team/isaaceng7) + diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..ca61e19eea 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,541 @@ # Developer Guide +## Table of Contents +- [Acknowledgements](#acknowledgements) +- [Design & Implementation](#design--implementation) + - [Architecture](#architecture) + - [Add Account](#implemented-add-account) + - [Remove Account](#implemented-remove-account) + - [Category Feature](#implemented-category-feature) + - [Process Transction](#implemented-process-transaction) + - [Remove Transactiom](#implemented-remove-transaction) + - [Edit Transaction](#implemented-edit-transaction) + - [Search Transaction](#implemented-search-transactions) + - [List Feature](#implemented-list-transactions) + - [Insights](#implemented-insights) +- [Product Scope](#product-scope) +- [User Stories](#user-stories) +- [Non-Functional Requirements](#non-functional-requirements) +- [Glossary](#glossary) +- [Instructions for manual testing](#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} +* [XChart](https://knowm.org/open-source/xchart/) - open source library, has been used in this project +as a data visualization tool. ## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Architecture + +The **Architecture Diagram** of BudgetBuddy is shown below. It explains the high-level design of the application.
+![](images/architectureDiagram.png) + +**Main components of the architecture:** + +The bulk of BudgetBuddy work is done by these following four components: + +* `BudgetBuddy` class: is the main class of the application, in charge of the app launch, + shut down and reading user's inputs. It invokes the loading and saving of data when the app is launched/shut down. +* `ui` package: consists of `Ui` class, which deals with all the printing/output to the user, + and also some reading of user's inputs for additional data. +* `parser` package: consists of the `Parser` class, makes sense of the data input by the user + to provide meaningful data for other methods. +* `storage` package: consists of `DataStorage` class, in charge of saving and loading of the `data` files. + +These components help to manipulate the `Transaction`, `TransactionList`, `Account` and `AccountManager` classes +which drives BudgetBuddy.

+ +### [Implemented] Add Account + +#### Description + +This method is used to add a new account to the list of accounts based on the user input provided. The input should +include the account name and initial balance. After the account is successfully added, the account balance is +initialized with the provided initial balance, and a unique account number is generated and assigned to the account. A +message is displayed to the user indicating the success of the operation. This allows users to manage multiple accounts +by adding new ones as needed. + +#### Parameters + +1. String input: A string containing the user input, which should include the account name and initial balance, + separated by specific delimiters. + +#### Design and Implementation + +The method starts by validating the syntax of the user input to ensure it contains the necessary delimiters for the +account name and initial balance. If the input does not meet the expected format, it throws an +InvalidArgumentSyntaxException. It then parses the input to extract the account name and initial balance. If either the +name or initial balance is missing or empty, an EmptyArgumentException is thrown. Additionally, if the initial balance +is not a valid double value, a NumberFormatException is thrown. + +After successfully parsing and validating the input, the method proceeds to generate a unique account number for the new +account. It ensures that the generated number is not already in use by any existing account. Once a unique number is +obtained, a new Account object is created with the generated account number, provided account name, and initial balance. +This new account is then added to the list of accounts, and its number is added to the list of existing account numbers +to ensure uniqueness. + +Finally, the method notifies the user of the successful addition of the account by displaying the details of the newly +created account. + +The following class diagram shows the associations between classes involved in adding an account. + +![](images/AddAccountClassDiagram.png) + +The following sequence diagram shows how the add account process works: + +![](images/addAccountDiagram.png)

+ +### [Implemented] Remove Account + +#### Description + +This method is used to remove an existing account from the system based on the account number provided by the user +input. It checks for the existence of the account in the system, and if found, it removes the account and any associated +transactions, displaying a message indicating successful removal. This allows users to manage their accounts effectively +by removing unnecessary or outdated accounts. + +#### Parameters + +1. String input: A string containing the user input, which should include the account number to be removed, parsed using + specific delimiters. + +#### Design and Implementation + +The method begins by validating the presence of the account number in the user input. If the format is incorrect, it +throws an `InvalidArgumentSyntaxException`. If the account number is missing or the format is incorrect (not a valid +integer), a `NumberFormatException` or `EmptyArgumentException` is thrown. + +After validating and parsing the account number, it checks if removing the account would result in no accounts left, +which is not allowed. If valid, it proceeds to remove the account by the account number. It also removes any associated +transactions with this account from the transaction list, ensuring no orphan transactions remain. + +Finally, the method updates the system's account list and transaction list to reflect the removal and notifies the user +of the successful operation by displaying relevant details of the removed account and transactions. + +The following sequence diagram shows how the remove account process works: + +![](images/RemoveAccounrSequenceDiagram.png) + +**Additional Notes** +The method ensures that the system's integrity is maintained by not allowing the last account to be removed, which is +handled gracefully with appropriate user notifications. The removal process also involves updating various components of +the system to reflect the changes accurately.

+ +### [Implemented] Category feature + +#### Description + +The Category feature empowers users to effectively categorize transactions based on their preferences. When initiating a +new transaction through the `Add` command, users are prompted to select a category from a predefined list. This ensures +organized and streamlined transaction management. + +#### Design and Implementation + +The implementation of the Category feature revolves around the integration of a `category` attribute within each +transaction object. This attribute is defined as a member of the `Category` enum class. + +Upon invoking the `Add` command, users are presented with a selection prompt featuring the available categories. User +input, typically in the form of a numerical identifier corresponding to a category within the enum class, facilitates +the assignment of the appropriate enum object to the transaction's category attribute.

+ +### [Implemented] Process transaction + +#### Description + +This method adds a transaction to the list of transactions based on the necessary input details given by the user. + +#### Parameters + +1. String input: A string containing the user input, which should include the `NAME`, `AMOUNT`, `DATE` and `TYPE` of the + transaction. +2. Account account: The account object associated with the transaction list. + +#### Design and Implementation + +1. ##### Syntax Validation: + + The method first checks whether the input string contains all necessary arguments ("/a/", "/t/", "/n/", "/$/", "/d/") + required for adding a transaction. If any argument is missing, it throws an InvalidAddTransactionSyntax exception. + +2. #### Transaction Parsing: + + It utilizes a parser object to parse the user input string into a Transaction object using the + parseUserInputToTransaction method. + +3. #### Assertion Checks: + + Assertions are used for debugging purposes to ensure that the parsed transaction and added transaction are not null. + If they are null, assertion errors are thrown. + +4. #### Category Assignment: + + If the category of the transaction is null, it prompts the user to choose a category from a list and assigns the + chosen category to the transaction. + +5. #### Transaction Addition: + + After parsing and category assignment, the transaction is added to the account using the addTransaction method. + +6. #### Feedback to User: + + Upon successful addition of the transaction, a message is printed to the user indicating the details of the added + transaction and the updated account balance. + +#### Exceptions: + +1. `InvalidTransactionTypeException`: This exception is thrown when the transaction type is not one of `income` + and `expense`. + +2. `InvalidAddTransactionSyntax`: This exception is thrown when the syntax of the add transaction is invalid. + +3. `EmptyArgumentException`: This exception is thrown when an empty argument is encountered. + +The following class diagram shows the associations between classes involved in processing a transaction. + +![](images/TransactionListDiagram.png) + +The following sequence diagram shows how an add transaction command works: +![](images/addTransactionDiagram.png)

+ +### [Implemented] Remove transaction + +#### Description + +This method is used to remove a transaction from the list of transactions based on the transaction ID provided +by the user. After the transaction is removed, the account balance is updated accordingly and a message is +displayed to the user indicating the success of the operation. This helps user to remove the transaction +from the list they added by mistake or those transactions they no longer need to keep track off. + +#### Parameters + +1. String input: A string containing the user input, which should include the transaction ID to be removed. +2. Account account: The account object associated with the transaction list. + +#### Design and Implementation + +The method first validates the user input to ensure it's not empty or null. If the input is invalid, it throws +an EmptyArgumentException. Next, it extracts the transaction ID from the input and verifies its integrity as a +valid integer. If the ID is invalid, a NumberFormatException is thrown. + +Once a valid transaction ID is obtained, the method calculates its corresponding index in the transactions +ArrayList by subtracting 1 from the provided ID, as ArrayList indices start from 0 . It then verifies +if the calculated index falls within the bounds of the ArrayList. If the index is out of bounds, an +InvalidIndexException is thrown. + +Upon successful validation, the method removes the transaction at the calculated index from the transactions +ArrayList. Subsequently, it updates the account balance to reflect the removed transaction. Finally, it +notifies the user of the successful removal along with displaying the details of the removed transaction. + +The following sequence diagram shows how a remove transaction goes works: + +![](images/removeTransactionDiagram.png)

+ +### [Implemented] Edit Transaction + +#### Description + +This method facilitates the editing of a transaction within the list of transactions associated with a +specific account. Users can edit transactions by providing the index of the transaction they wish to modify +along with the updated transaction details. After the edit operation is completed, the system updates the +transaction accordingly and notifies the user of the successful operation. This feature enhances user +flexibility by allowing them to correct erroneous transactions or update transaction details as needed. + +#### Parameters + +1. String input: A string representing user input, including the index of the transaction to be edited and + the updated transaction details. +2. Account account: The account object associated with the transaction list. + +#### Design and Implementation + +The processEditTransaction method follows a structured approach to ensure the successful editing of +transactions while maintaining data integrity: + +1. Input Validation: The method begins by validating the user input to ensure it is not empty or null. If + the input is invalid, an EmptyArgumentException is thrown to prompt the user to provide valid input. +2. Transaction Index Extraction: After validating the input, the method extracts the index of the + transaction to be edited from the input string. It ensures the extracted data is a valid integer; otherwise, a + NumberFormatException is thrown to indicate invalid input. +3. Index Calculation: Once a valid transaction index is obtained, the method calculates the corresponding + index in the transactions ArrayList. As ArrayList indices start from 0, the provided index is decremented + by 1 to align with the ArrayList index. +4. Index Bounds Verification: The method verifies whether the calculated index falls within the bounds of + the transactions ArrayList. If the index is out of bounds, an InvalidIndexException is thrown to notify the user of + the invalid index provided. +5. Transaction Editing: Upon successful validation, the method retrieves the transaction object at the + calculated index from the transactions ArrayList. It prompts the user to input the updated transaction details + through the UserInterface.getEditInformation() method. The edited transaction is then parsed using the + parser.parseTransactionType() method to ensure its validity and association with the provided account. Finally, the + edited transaction replaces the original transaction at the specified index in the transactions ArrayList using the + transactions.set() method. +6. User Notification: After editing the transaction, the method notifies the user of the successful + operation by displaying a message through the UserInterface.printUpdatedTransaction() method. + +Sequence Diagram +The following sequence diagram illustrates the sequence of interactions involved in the editing of a transaction: +![](images/processEditTransactionDiagram.png)

+ +### [Implemented] Search Transactions + +### Description + +This method enables users to search for transactions based on a keyword. Users provide a keyword, and the system +searches through transaction descriptions, amounts, categories, and dates to find matches. The search results, +along with their corresponding indices in the transactions list, are displayed to the user. + +#### Design and Implementation + +1. **Keyword Extraction:** The method extracts the keyword from the user input. + +2. **Search Process:** It iterates through the list of transactions, checking if any transaction's + description, amount, category, or date contains the keyword. Matches are added to a list of search + results along with their corresponding indices in the transactions list. + +3. **Output Generation:** Once the search process is completed, the method generates output by displaying the + search results along with their indices to the user. + +4. **Exception Handling:** The method handles exceptions such as an empty keyword input or any unexpected + errors during the search process. Proper error messages are displayed to the user in case of exceptions. + +Example Algorithm: + +``` +searchTransactions(input) + 1. Extract the keyword from the user input. + 2. Initialize empty lists for search results and indices. + 3. For each transaction in transactions do: + 1. Convert transaction description to lowercase (description_lower). + 2. Convert transaction amount to string (amount_str). + 3. Convert category name to lowercase (category_name). + 4. Convert transaction date to string (date_str). + 5. If keyword is present in description_lower, amount_str, category_name_lower, or date_str then: + - Add the transaction to the search results list. + - Add the index of the transaction in transactions to the indices list. + 4. Display the search results along with their indices to the user. + 5. Catch ArrayIndexOutOfBoundsException: + - Print "Invalid search input." + 6. Catch Exception: + - Print the exception message. +``` + +### [Implemented] List Transactions + +### Description + +The list feature allows users to view their existing transactions. This feature includes viewing all the transactions, +past week's transactions, past month's transactions, transactions from a specified date range, transactions from a +specified account and transactions of a particular category. + +#### Design and Implementation + +This feature is facilitated through the `TransactionList#processList`, and it is designed to ensure successful viewing +of the desired list as inputs are required in a bite-sized manner. + +This method first executes the `UserInterface#printListOptions` to show users the list options and their indexes which +is needed for their inputs. The method will throw an InvalidIndexException if the input is out of the range (range 1-6). +Depending on the list option chosen by the user, the case statement of the `TransactionList#processList` will run, and +execute the method of the corresponding option. Different methods would have different prompts as more information is +required from the user. For the example of custom date transactions, `TransactionList#getCustomDateTransactions` will +call the methods: `UserInterface#getStartDate` and `UserInterface#getEndDate` in order to obtain the desired date range +from the user. Once all the required information is gathered for the particular option, an ArrayList will be created and +the desired transactions will be added into that ArrayList. Then, this ArrayList will be printed out, displaying the +transactions of the chosen option. + +Sequence Diagram
+The following sequence diagram illustrates the sequence of interactions involved in the editing of a transaction: +![](images/processList.png)

+ +### [Implemented] Insights + +#### Description + +This feature provides insights into the categorized expenses and incomes of the user. It utilizes the Insight +class to calculate and display pie charts representing the distribution of expenses and incomes across +different categories.
+ +#### Design and Implementation + +1. The displayCategoryInsight method iterates through the list of transactions and calculates the total income + and expense amounts for each category. It then calls the displayPieChart method to visualize these insights + using pie charts. + +2. The displayPieChart method creates separate pie charts for income and expense categories using the XChart + library. It customizes the appearance of the charts and adds series for each category with their respective + income or expense amounts. + +3. The indexOf method is a private helper function used to find the index of a specific category within an + array + of categories. + +4. The closeInsightFrames method is responsible for closing any open frames related to insights, specifically + targeting frames related to income and expense insights to ensure proper cleanup and resource management. + +The following is the class diagram for Insights class + +![](images/insightDiagram.png) ## Product scope -### Target user profile -{Describe the target user profile} +### Target user profile: -### Value proposition +* has a need to manage significant number of day-to-day transactions +* prefer desktop apps over other types +* can type fast +* prefers typing to mouse interactions +* is reasonably comfortable using CLI apps -{Describe the value proposition: what problem does it solve?} +### Value proposition: + +* manage daily transactions faster than a typical mouse/GUI driven app ## 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| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|----------------------------------------------------|-------------------------------------------------------| +| v1.0 | user | Update my daily expense | Manage my transactions | +| v1.0 | user | Exit from the interface | Stop the application | +| v1.0 | user | Know how to communicate with the bot | Use it effectively | +| v1.0 | user | View my past transactions | Keep track of them | +| v1.0 | user | Delete a transaction | Remove a transaction I added by mistake | +| v1.0 | user | Add income as well as expense transactions | Track my balance | +| v2.0 | user | Keep track of my balance | Know how much money I have left | +| v2.0 | user | Choose the date range to view my transactions | View transactions that I am interested in only | +| v2.0 | user | Track multiple balances such as wallet, debit card | Know how much money I have left in all accounts | +| v2.0 | user | Get a quick view of my past week's transactions | Obtain a quick overview of my recent spending history | +| v2.0 | user | Categorize my transactions | Get insights on each category | ## Non-Functional Requirements -{Give non-functional requirements} +1. Should work on any *mainstream OS* as long as it has Java `11` or above installed. +2. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be + able to accomplish most of the tasks faster using commands than using the mouse. ## Glossary -* *glossary item* - Definition +* **Mainstream OS**: Windows, Linux, Unix, macOS ## Instructions for manual testing -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +### Add transaction +With the help of help guide you can add a transaction (assuming account number is 5431) + * Run the following command: `add /a/5431 /t/Income /n/March Salary /$/10000 /d/01-03-2024 /c/8` + Expected : Received acknowledgement and transaction is added to the list. + * Run the following command: `add /a/5431 /t/new /n/March Salary /$/10000 /d/01-03-2024 /c/8` + Expected: Error occurred with a message. Transaction is not added + * Run the following command : `add /a/5431 /t/Income /n/March Salary /$/10000 /d/01-20-2024 /c/8` + Expected: Error occurred due to date format. Transaction is not added + * Run the following command : `add /a/5431 /t/Income /n/March Salary /$/10000 /d/01-03-2024 /c/10` + Expected: Invalid category. Transaction is not added. + +### Edit Transaction +With the help of help guide you can edit a transaction (assuming there are only 2 transactions in the history) +* Run the following command: `edit 3` + Expected: Error occurred due to index id is out of the range. +* Run the following command : `edit 0` + Expected: Error occurred due to index id is out of the range. +* Run the following command : `add-acc /n/DBS Savings /$/hundred` + Expected: Error occurred due to invalid initial balance. Initial balance should be double not a string. +* Run the following command: `edit 1` + Expected : Details of the transaction is shown, and new input for each argument. + + +### Add account +With the help of help guide you can add a new account +* Run the following command: `add-acc /n/DBS Savings /$/10000` + Expected : Received acknowledgement and account is added successfully. +* Run the following command: `add-acc /n//$/10000` + Expected: Error occurred due to empty account name. Account not added. +* Run the following command : `add-acc /n/DBS Savings /$/` + Expected: Error occurred due to missing initial balance. Account not added. +* Run the following command : `add-acc /n/DBS Savings /$/hundred` + Expected: Error occurred due to invalid initial balance. Initial balance should be double not a string. + Account not added + + +### Deleting a transaction +1. Deleting a transaction with multiple existing transactions + 1. Prerequisites: List all transactions using the `list` command followed by `1`. **Multiple transactions** in the list. + 2. Test case: `delete 1` + Expected: First transaction is deleted from the transaction history. Account balance is updated. Details of the deleted transaction is shown in the status message. + 3. Test case: `delete 0` + Expected: No transaction is deleted. Error details shown in the status message. + 4. Test case: other incorrect delete commands: `delete abc`, `deleteee` + Expected: Similar to point 3. + +2. Deleting a transaction with only one existing transaction + 1. Prerequisites: List all transactions using the `list` command followed by `1`. **Only one** transaction in the list. + 2. Test case: `delete 1` + Expected: First transaction is deleted from the transaction history. Account balance is updated. Details of the deleted transaction is shown in the status message. + 3. Test case: `delete 2` + Expected: No transaction is deleted. Error details shown in the status message. + 4. Test case: `delete 0` + Expected: No transaction is deleted. Error details shown in the status message. + +3. Deleting a transaction with no existing transaction + 1. Prerequisites: List all transactions using the `list` command followed by `1`. **No** transaction in the list. + 2. Test case: `delete 1` + Expected: No transaction is deleted. Error details shown in the status message. + 3. Test case: `delete 0` + Expected: No transaction is deleted. Error details shown in the status message. + + +### View transaction history +1. Viewing transaction history of all transactions, past week transactions or past month transactions. + 1. Prerequisites: There is at least one existing transaction. View list options using the `list` command. + 2. Test case: `1` + Expected: All existing transactions is shown in the status message. + 3. Test case: `2` + Expected: Existing transactions from the past week is shown in the status message. + 4. Test case: `3` + Expected: Existing transactions from the past month is shown in the status message. + 5. Test case: integers of `4` to `6` + Expected: Contain prompts before generating different transaction lists in status message. + 6. Test case: other integers or incorrect inputs: `10`, `-1`, `abc`. + Expected: No list is generated. Error details shown in the status message. + +2. Viewing transaction from custom date range + 1. Prerequisites: There is at least one existing transaction that is within desired date range. View list options using the `list` command, followed by `4` + 2. Test case: `01-01-2024` followed by `01-03-2024` + Expected: All existing transactions within 01-01-2024 and 01-03-2024 shown in status message. + 3. Test case: `01012024` followed by `01032024` + Expected: No list is generated. Error details shown in the status message. + 4. Test case: other incorrect inputs: `01-01-24`, `01-Mar-2024` + Expected: No list is generated. Error details shown in the status message. + +### Delete an account +1. Delete an existing account + 1. Prerequisites: List all accounts using the `list-acc`. There is at least one existing account. + 2. Test case: `delete-acc ACCOUNT_NUMBER` + Expected: Deletes the account and all existing transactions under it. Account details and transactions are shown in the status message. + 3. Test case: other incorrect inputs that is not the ACCOUNT_NUMBER: `delete-acc abc`, `delete-acc 2134321`, `delete-acc `. + Expected: No account is deleted. Error details shown in the status message. + + +### Data Storage +1. Saving the transactions and accounts + 1. Test case: Add a valid transaction, close the program and reopen the program
+ Expected: The previous transaction and the accounts are still available in the + transaction list to which the user can append more. +2. Deleting transactions and accounts + 1. Prerequisite: Have at least 1 transaction in the transaction list. + 2. Test case: Delete a transaction, close the program and reopen it.
+ Expected: The previous transaction is not available in the + transaction list, and it has been permanently deleted. + 3. Test case: Delete an account, close the program and reopen it.
+ Expected: The previous account and all the transactions associated with it are + not available in the transaction list, and they have been permanently deleted. +3. Dealing with corrupted data file. + 1. Prerequisite: Have some transactions in the transaction list. + 2. Test case: Remove any parameter of a transaction in the data file.
+ Expected: File Corrupted Error is triggered and all the data are + erased. The user is also notified of this error. + 3. Test case: Add a new delimiter ',' to the data file.
+ Expected: File Corrupted Error is triggered and all the data are + erased. The user is also notified of this error. + 4. Test case: An account (with at least 1 transaction) is deleted from the storage
+ Expected: File Corrupted Error is triggered and all the data are + erased. The user is also notified of this error. + diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..704ffec9ea 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ -# Duke +# BudgetBuddy -{Give product intro here} +BudgetBuddy is a **desktop app for managing personal finances, optimized for use via a Command Line Interface** (CLI). +It offers the tracking of income and expenses of multiple accounts and even provides insights of your financial +activities. Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..e508f4c2d7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,315 @@ # User Guide +## Table of Contents +- [Introduction](#introduction) +- [Quick Start](#quick-start) +- [Features](#features) + - [Viewing help: `help`](#viewing-help-help) + - [Adding a transaction: `add`](#adding-a-transaction-add) + - [View transaction history: `list`](#view-transaction-history-list) + - [Deleting a transaction: `delete`](#deleting-a-transaction-delete) + - [Edit a transaction: `edit`](#edit-a-transaction-edit) + - [Search for a transaction: `search`](#search-for-a-transaction-search) + - [Add an account: `add-acc`](#add-an-account-add-acc) + - [List all accounts: `list-acc`](#list-all-accounts-list-acc) + - [Delete an account: `delete-acc`](#delete-an-account-delete-acc) + - [Edit an account: `edit-acc`](#edit-an-account-edit-acc) + - [View transaction insights: `insights`](#view-transaction-insights-insights) + - [Exiting the program: `bye`](#exiting-the-program-bye) + - [Saving the data](#saving-the-data) + - [Editing the data file](#editing-the-data-file) +- [FAQ](#faq) +- [Command Summary](#command-summary) + ## Introduction -{Give a product intro} +BudgetBuddy is a **desktop app for managing personal finances, optimized for use via a Command Line Interface** (CLI). +It offers the tracking of income and expenses of multiple accounts and even provides insights of your financial +activities. ## Quick Start -{Give steps to get started quickly} +1. Ensure that you have Java `11` or above installed. +2. Down the latest version of `budgetbuddy.jar` + from [here](https://github.com/AY2324S2-CS2113-T15-2/tp/releases/latest). +3. Copy the file to the folder you want to use as the _home folder_ for your BudgetBuddy. +4. Open a command terminal, `cd` into the folder you added the jar file to, and use the `java -jar budgetbuddy.jar` + command to + run the application. +5. When the application is **first run**, BudgetBuddy will prompt the user to **create a new account**, prompting the + user to + add an **account name** and **initial balance**. Type the details in the terminal and press Enter to confirm. +6. Subsequently, users may type the command into the terminal and press Enter to execute it. e.g. typing **`help`** and + pressing + Enter will prompt the help feature. +7. Refer to the [Features](#features) below for details of each command. + +## Features + +> [!NOTE] +> **The following are notes about the command format:** +* Words in `UPPER_CASE` are the parameters to be supplied by the user. + e.g `delete TRANSACTION_ID`, `TRANSACTION_ID` is a parameter which can be used as `delete 1` +* Parameters can be in any order. + e.g if the command specifies `/n/ACCOUNT_NAME /$/INITIAL_BALANCE`, `/$/INITIAL_BALANCE /n/ACCOUNT_NAME` is also acceptable. +* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `list-acc` and `bye`) will be ignored. + e.g if the command specifies `help abc`, it will be interpreted as `help`. + +### Viewing help: `help` + +Shows the instructions for using BudgetBuddy.
+ +This command gives the list to search for commands, specific for each task. The commands `help all` and +`help acc` covers the basic structure of all commands. Instructions for command-specific help will be +provided in the `help all` and `help acc` accordingly. + +**Format:** `help`

+ +### Adding a transaction: `add` + +Adds a transaction into the transaction list of the specified account. + +**Parameters:** Account Number, Transaction Type, Name, Amount, Date, Category + +**Format:** `add /a/ACCOUNT_NUMBER /t/TRANSACTION_TYPE /n/NAME /$/AMOUNT /d/DATE /c/CATEGORY` + +* The `ACCOUNT_NUMBER` can be viewed using the command `list-acc`. +* The `TRANSACTION_TYPE` includes **Expense** or **Income** ONLY. +* The `AMOUNT` is in dollars ($). +* The `DATE` should be in the format **DD-MM-YYYY**. +* The `CATEGORY` is an integer. The categories are mapped to the following integers: + - 1 - Dining + - 2 - Groceries + - 3 - Utilities + - 4 - Transportation + - 5 - Healthcare + - 6 - Entertainment + - 7 - Rent + - 8 - Salary + - 9 - Others + +**Example of usage:** + +* `add /a/5431 /t/Income /n/March Salary /$/10000 /d/01-03-2024 /c/8` + +* `add /n/New iPhone /$/2000 /c/9 /t/Expense /a/5431 /d/20-03-2024` + +_Successful add feature output:_
+![](images/successful_add_feature.png)

+ +### View transaction history: `list` + +List the existing transactions. List feature includes options: + +1. All Transactions +2. Past Week Transactions - list transactions from the past 7 days +3. Past Month Transactions - list transactions from the past month +4. Custom Date Transactions - list transactions between the specified dates(inclusive) +5. Account Transactions - list all transactions in the specified account +6. Category Transactions - list all transactions in the category type + +**Format:** `list` + +**Example of usage:** + +* `list` followed by `1` to view All Transactions. +* `list` followed by `2` to view Past Week Transactions. +* `list` followed by `3` to view Past Month Transactions. +* `list` followed by `4` followed by the start date `01-01-2024` followed by the end date `31-03-2024` + to view Custom Date Transactions from 01-01-2024 to 31-03-2024. +* `list` followed by `5` followed by account number `ACCOUNT_NUMBER` to view transactions from that account. +* `list` followed by `6` followed by category number `CATEGORY_NUMBER` to view transactions of that category. + +_List feature options:_
+![](images/list_options.png) + +_Successful list feature (custom date) example:_
+![](images/successful_list_feature.png)

+ +### Deleting a transaction: `delete` + +Removes a transaction from transaction history. + +**Parameters:** Transaction ID + +**Format:** `delete TRANSACTION_ID` + +**_Note:_** + +* The `TRANSACTION_ID` is an integer value ranges from one to the size of the transaction history (index + ID of the last transaction). +* The `TRANSACTION_ID` can be viewed using the command `list` followed by `1`. + +**Example of usage:** +`delete 1` + +_Successful delete feature example:_ +![](images/successful_delete_transaction.png)

+ +### Edit a transaction: `edit` + +Edits the details of an existing transaction. + +**Parameters:** Transaction ID + +**Format:** `edit TRANSACTION_ID` + +**_Note:_** + +* The `TRANSACTION_ID` is an integer value ranges from one to the size of the transaction history (index + ID of the last transaction). +* The `TRANSACTION_ID` can be viewed using the command `list` followed by `1`. +* Edit transaction will only update the existing entry, so it won't change the index ID of that transaction. + The edited transaction will still be accessible from the same index ID. + +**Example of usage:** +`edit 2` + +* Then the user will be asked to edit each information from that specific transaction one by one. + +_Successful edit feature example:_ +![](images/successful_edit_transaction.png)

-1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +### Search for a transaction: `search` -## Features +Search for a list of transactions matching the keyword. -{Give detailed description of each feature} +**Parameters:** keyword -### Adding a todo: `todo` -Adds a new item to the list of todo items. +**Format:** `search KEYWORD` -Format: `todo n/TODO_NAME d/DEADLINE` +**_Note:_** -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +* The `KEYWORD` can be any value representing transaction description, category, transaction amount or + transaction date. +* Search transaction will list out the matching transactions along with their true **index ID**. This can + be used in `edit` or `delete` command. +* Keywords are case-insensitive so if there is no matching transactions, the user will be notified. +* This feature will search from the whole transaction history rather than a specific account to ease the + usage of the BudgetBuddy. -Example of usage: +**Example of usage:** +`search salary` -`todo n/Write the rest of the User Guide d/next week` +* Then the user will be asked to edit each information from that specific transaction one by one. -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +_Successful edit feature example:_
+![](images/successful_search.png)

+ +### Add an account: `add-acc` + +Adds a new account with a specified initial balance. + +**Parameters:** Account Name, Initial Balance + +**Format:** `add-acc /n/ACCOUNT_NAME /$/INITIAL_BALANCE` + +**_Note:_** + +* The `INITIAL_AMOUNT` is in dollars ($). + +**Example of Usage:** +`add-acc /n/DBS Savings /$/10000` + +_Successful add-acc feature output:_
+![](images/successful_add_acc_feature.png)

+ +### List all accounts: `list-acc` + +List all the existing accounts. + +**Format:** `list-acc` + +_Successful list-acc feature output:
_ +![](images/successful_list_acc_feature.png)

+ +### Delete an account: `delete-acc` + +Removes an account and removes all its transactions. + +**Parameters:** Account Number. + +**Format:** `delete ACCOUNT_NUMBER` + +**_Note:_** + +* The `ACCOUNT_NUMBER` can be viewed using the command `list-acc`. + +**Example of usage:** +`delete-acc 5431` + +_Successful delete-acc feature output:_
+![](images/successful_delete_acc_feature.png)

+ +### Edit an account: `edit-acc` + +Edits the details of an existing account. + +**Parameters:** Account Number + +**Format:** `edit-acc ACCOUNT_NUMBER` + +**_Note:_** + +* The `ACCOUNT_NUMBER` can be viewed using the command `list-acc`. + +**Example of usage:** +`edit-acc 5431` + +_Successful edit-acc feature output:_
+![](images/successful_edit_acc_feature.png)

+ +### View transaction insights: `insights` + +View the insights of all the transactions listed so far using a pie chart. Two pie charts are displayed, +one for each type (i.e. `income` and `expense`). The pie charts show the percentage of total amount transferred +in a particular category among all categories. + +**Format:** `insights`

+ +### Exiting the program: `bye` + +Exits BudgetBuddy. + +**Format:** `bye`

+ +### Saving the data + +BudgetBuddy data are saved in the hard disk automatically when the user exits the program. There is no need to save the +data manually. The data will be loaded automatically when the user runs the program again.

+ +### Editing the data file + +BudgetBuddy data are saved as two txt files `[JAR file location]/data/accounts.txt` +and `[JAR file location]/data/transactions.txt`. Advanced users are welcome to update the data directly by editing the +data files. + +**_Caution!:_** If your changes to the data file makes its format invalid, BudgetBuddy will discard all data and start with +an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the BudgetBuddy to behave in unexpected ways (e.g., if a value entered is outside +the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. ## FAQ -**Q**: How do I transfer my data to another computer? +**Q**: How do I transfer my data to another Computer? -**A**: {your answer here} +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains +the data of your previous BudgetBuddy home folder. ## Command Summary -{Give a 'cheat sheet' of commands here} - -* Add todo `todo n/TODO_NAME d/DEADLINE` +* View help `help` +* View help for all commands `help all`. This can be used to see all the commands related to transactions +* View help for accounts `help acc`. This can be used to see all the commands related to account +* Further help for each transaction command will be provided in the `help all` +* Add transaction `add /a/ACCOUNT_NUMBER /t/TRANSACTION_TYPE /n/NAME /$/AMOUNT /d/DATE /c/CATEGORY` +* List transactions `list` +* Delete transaction `delete TRANSACTION_ID` +* Edit transaction `edit TRANSACTION_ID` +* Search transaction `search KEYWORD` +* Add account `add-acc /n/ACCOUNT_NAME /$/INITIAL_BALANCE` +* List accounts `list-acc` +* Delete account `delete-acc ACCOUNT_NUMBER` +* Edit account `edit-acc ACCOUNT_NUMBER` +* View insights `insights` +* Exit program `bye` diff --git a/docs/diagrams/AddAccountClassDiagram.puml b/docs/diagrams/AddAccountClassDiagram.puml new file mode 100644 index 0000000000..31d508e0f5 --- /dev/null +++ b/docs/diagrams/AddAccountClassDiagram.puml @@ -0,0 +1,76 @@ +@startuml +skinparam classAttributeIconSize 0 +hide abstract circle +hide class circle +hide enum circle + +' Define the Account package and Account class +package budgetbuddy.account { + class Account { + -balance: double + +Account() + +Account(balance: double): void + +getBalance(): double + +setBalance(balance: double): void + } +} + +' Existing TransactionList package with new Account association +package budgetbuddy.transaction { + class TransactionList { + -transactions: ArrayList + -parser: Parser + -dataStorage: DataStorage + +TransactionList(transactions: ArrayList): void + +processTransaction(input: String, account: Account): void + +updateBalance(account: Account): void ' Added method for updating balance + } +} + +' Existing Parser package +package budgetbuddy.parser { + object Parser { + +parseUserInputToTransaction(input: String, account: Account): Transaction + } +} + +' Existing DataStorage package +package budgetbuddy.datastorage { + object DataStorage { + +saveTransactions(transactionArrayList: ArrayList): void + +getBalance(): double ' Presumed method for getting balance for an account + } +} + +' Existing Transaction Type and Category packages and classes +package budgetbuddy.transaction.type { + abstract class Transaction { + -description: String + -amount: double + -category: Category + -date: LocalDate + +Transaction(accountNumber: int, accountName: String, + description: String, amount: double, date: String) + } +} + +package budgetbuddy.categories { + enum Category <> { + +Category(categoryNum: int, categoryName: String) + +getCategoryName(): String + +getCategoryNum(): int + } +} + +' Define relationships +TransactionList -right-> "1" Parser: "uses " +TransactionList -down-> "1" DataStorage: "uses " +TransactionList ..> Transaction: "creates " +Transaction --> "1" Category: "categorized by " +Parser ..> Transaction: "parses into " +Parser ..> Category: "uses " +DataStorage ..> Account: "saves and retrieves account balance " +Account <-- TransactionList: "updated by " + +' Additional relationships for Account management might be needed depending on the context +@enduml diff --git a/docs/diagrams/RemoveAccounrSequenceDiagram.puml b/docs/diagrams/RemoveAccounrSequenceDiagram.puml new file mode 100644 index 0000000000..dadb44d0bb --- /dev/null +++ b/docs/diagrams/RemoveAccounrSequenceDiagram.puml @@ -0,0 +1,30 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +actor User +participant "AccountManager" as AM +participant "Parser" as P +participant "TransactionList" as TL +participant "UserInterface" as UI + +User -> AM: removeAccount(input, TL) +AM -> AM: validateSyntax(input) +AM -> P: parseRemoveAccount(input) +activate P +P --> AM: accountNumber +deactivate P +AM -> AM: getAccountByAccountNumber(accountNumber) +AM -> TL: removeTransactionsByAccountNumber(accountNumber) +activate TL +TL --> AM: transactionsRemoved +deactivate TL +AM -> AM: removeAccount(accountNumber) +AM -> UI: printDeleteAccountMessage(removedAccount, transactionsRemoved) +activate UI +UI --> AM: Display removed account message +AM --> User: Confirmation message + +deactivate UI +@enduml diff --git a/docs/diagrams/TransactionListDiagram.puml b/docs/diagrams/TransactionListDiagram.puml new file mode 100644 index 0000000000..b964603c52 --- /dev/null +++ b/docs/diagrams/TransactionListDiagram.puml @@ -0,0 +1,51 @@ +@startuml +skinparam classAttributeIconSize 0 +hide abstract circle +hide class circle +hide enum circle +package budgetbuddy.transaction{ + class TransactionList { + -transactions: ArrayList + -parser: Parser + -dataStorage: DataStorage + +TransactionList(transactions: ArrayList): void + +processTransaction(input: String, account: Account): void + } +} +package budgetbuddy.parser{ + object Parser { + +parseUserInputToTransaction(input: String, account: Account): Transaction + } +} + +package budgetbuddy.datastorage{ + object DataStorage { + +saveTransactions(transactionArrayList: ArrayList): void + } +} + +package budgetbuddy.transaction.type { + abstract class Transaction{ + -description: String + -amount: double + -category: Category + -date: LocalDate + +Transaction(accountNumber: int, accountName: String, + description: String, amount: double, date: String) + } +} + +package budgetbuddy.categories { + enum Category <> { + +Category(categoryNum: int, categoryName: String) + +getCategoryName(): String + +getCategoryNum(): int + } +} + +TransactionList -> "1" Parser: " {association} " +TransactionList ---> "1" DataStorage: {association} +TransactionList ..> Transaction: {dependency} +Transaction --> "1" Category: {association} +Parser ...> Category: <> +@enduml \ No newline at end of file diff --git a/docs/diagrams/addAccountDiagram.puml b/docs/diagrams/addAccountDiagram.puml new file mode 100644 index 0000000000..d724ac26d2 --- /dev/null +++ b/docs/diagrams/addAccountDiagram.puml @@ -0,0 +1,23 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +actor User +participant "AccountManager" as AM +participant "Parser" as P +participant "UserInterface" as UI + +User -> AM: processAddAccount(input) +AM -> AM: validateSyntax(input) +AM -> P: parseAddAccount(input) +activate P +P --> AM: parsedData +deactivate P +AM -> AM: addAccount(parsedData[0], parsedData[1]) +AM -> UI: printAddAccountMessage(newAccount) +activate UI +UI --> AM: Display added account message +AM --> User : Confirmation message +deactivate UI +@enduml \ No newline at end of file diff --git a/docs/diagrams/addTransaction.puml b/docs/diagrams/addTransaction.puml new file mode 100644 index 0000000000..85ad6f4e44 --- /dev/null +++ b/docs/diagrams/addTransaction.puml @@ -0,0 +1,36 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +actor User +participant ":TransactionManager" as TM +participant ":Parser" as P +participant "UserInterface" as UI + +User -> TM: processTransaction(input, account) +TM -> TM: validateSyntax(input) +TM -> P: parseUserInputToTransaction(input, account) +activate P +P -> P: checkCategory(transaction) + +alt Category is null + P -> UI: listCategories() + activate UI + UI --> User: Display category options + User --> UI: categoryNum + UI --> P: getCategoryNum() + deactivate UI +end + +P --> TM: transaction +deactivate P + + +TM -> TM: addTransaction(transaction) +TM -> UI: printAddMessage(transaction, account.balance) +activate UI +UI --> TM : Display added transaction message +TM --> User: Confirmation message +deactivate UI +@enduml \ No newline at end of file diff --git a/docs/diagrams/insightDiagram.puml b/docs/diagrams/insightDiagram.puml new file mode 100644 index 0000000000..c3e378c63e --- /dev/null +++ b/docs/diagrams/insightDiagram.puml @@ -0,0 +1,55 @@ +@startuml +skinparam classAttributeIconSize 0 +hide abstract circle +hide class circle +hide enum circle +package budgetbuddy.insight { + class Insight { + +displayCategoryInsight(transactionArrayList: ArrayList): void + -displayPieChart(categoryArray: Category[], incomeArray: Double[], expenseArray: Double[]): void + -indexOf(array: Category[], target: Category): int + +closeInsightFrames(): void + } +} + +package budgetbuddy.categories { + enum Category <> { + +Category(categoryNum: int, categoryName: String) + +getCategoryName(): String + +getCategoryNum(): int + } +} + +package budgetbuddy.transaction.type { + abstract class Transaction{ + -category: Category {dependency} + +Transaction(accountNumber: int, accountName: String, + description: String, amount: double, date: String) + +getAmount(): double + +getCategory(): Category + +setCategory(category: Category): void + {abstract} +getTransactionType() : String + } +} + + +class PieChart +class PieChartBuilder +class XChartPanel +class PieStyler +class JFrame { + +dispose(): void +} +class JPanel + +Insight ...> Category : " dependency " +Insight ..> Transaction: dependency +Transaction --> "*" Category: association +Insight ..> JFrame :creates 2 +Insight ..> JPanel: creates 2 +Insight ..> PieChart: uses 2 +Insight ..> PieChartBuilder: uses 2 +Insight ..> XChartPanel :uses 2 +Insight ..> PieStyler: uses 2 + +@enduml diff --git a/docs/diagrams/processEditTransactionDiagram.puml b/docs/diagrams/processEditTransactionDiagram.puml new file mode 100644 index 0000000000..4b7968dcb7 --- /dev/null +++ b/docs/diagrams/processEditTransactionDiagram.puml @@ -0,0 +1,49 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +actor User +participant "TransactionList" as TL +participant "UserInterface" as UI +participant ":AccountManager" as AM +participant ":Parser" as P +participant "EmptyArgumentException" as EAE +participant "InvalidIndexException" as IIE +participant "NumberFormatException" as NFE +User -> TL: editTransaction [edit index] + +alt edit index is empty + TL -> EAE: throw EmptyArgumentException("edit index") +end +alt index is not an integer + TL -> NFE : throw NumberFormatException(edit_index) +end +TL -> TL: index = parseInt(data) +alt index is within bounds + TL -> UI: + activate UI + UI --> TL: newTransaction + deactivate UI + TL -> P: parseEditTransaction(newTransaction, account) + activate P + P --> TL: updatedTransaction + deactivate P + + TL -> AM: getAccountByAccountNumber(accountNumber) + activate AM + AM --> TL: account + deactivate AM + + TL -> TL: setTransaction(updatedTransaction) + + TL -> UI: + activate UI + UI --> TL: Display updated transaction + deactivate UI + TL --> User: Display Output +else + TL -> IIE: throw InvalidIndexException(transaction_list_size) +end + +@enduml diff --git a/docs/diagrams/processList.puml b/docs/diagrams/processList.puml new file mode 100644 index 0000000000..bdf6a761ba --- /dev/null +++ b/docs/diagrams/processList.puml @@ -0,0 +1,107 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +actor User +participant ":TransactionManager" as TM +participant ":UserInterface" as UI +participant "LocalDate" as LD +participant ":AccountManager" as AM + +User -> TM: processList() +TM -> UI: printListOptions() +activate UI +UI --> User: Display list options +deactivate UI +User -> TM: selectOption(option) +TM -> TM: determineAction(option) + +alt 1: All Transactions + TM -> TM: printTransactions() + TM -> UI: printTransactions() + activate UI + UI --> TM: Display all transactions + UI --> User: Confirmation message + deactivate UI +else 2: Past Week Transactions + TM -> LD: now() + activate LD + LD --> TM: today + deactivate LD + TM -> TM: calculateStartDate("week") + TM -> TM: getPastTransactions(startDate) + TM -> UI: printPastTransactions(transactions, "week") + activate UI + UI --> TM: Display past week transactions + TM --> User: Confirmation message + + deactivate UI +else 3: Past Month Transactions + TM -> LD: now() + LD --> TM: today + TM -> TM: calculateStartDate("month") + TM -> TM: getPastTransactions(startDate) + TM -> UI: printPastTransactions(transactions, "month") + activate UI + UI --> TM: Display past month transactions + TM --> User: Confirmation message + + deactivate UI +else 4: Custom Date Transactions + TM -> UI: getStartDate() + activate UI + UI --> TM: startDate + deactivate UI + TM -> UI: getEndDate() + activate UI + UI --> TM: endDate + deactivate UI + TM -> TM: parseDates(startDate, endDate) + TM -> TM: getCustomDateTransactions(startDate, endDate) + TM -> UI: printCustomDateTransactions(transactions) + activate UI + UI --> TM: Display custom date transactions + TM --> User: Confirmation message + + deactivate UI +else 5: Account Transactions + TM -> UI: printAccountList(accounts) + activate UI + UI --> User: Display account list + deactivate UI + TM -> UI: getSelectedAccountNumber(accounts) + activate UI + UI --> TM: accountNumber + deactivate UI + TM -> AM: getAccountByAccountNumber(accountNumber) + activate AM + AM --> TM: account + deactivate AM + TM -> TM: getAccountTransactions(transactions, accountNumber) + TM -> UI: printAccountTransactions(accountTransactions, accountName, accountNumber) + activate UI + UI ---> TM: Display account transactions + TM ---> User: Confirmation message + + deactivate UI +else 6: Category Transactions + TM -> UI: listCategories() + activate UI + UI --> User: Display category list + deactivate UI + TM -> UI: getSelectedCategory() + activate UI + UI --> TM: categorySelected + deactivate UI + TM -> TM: fromNumber(category) + TM -> TM: getCategoryName() + TM -> TM: getCategoryTransactions(transactions, categorySelected) + TM -> UI: printCategoryTransactions(categoryTransactions, categoryName) + activate UI + UI --> TM: Display category transactions + TM --> User: Confirmation message + + deactivate UI +end +@enduml \ No newline at end of file diff --git a/docs/diagrams/removeTransactionDiagram.puml b/docs/diagrams/removeTransactionDiagram.puml new file mode 100644 index 0000000000..0156798974 --- /dev/null +++ b/docs/diagrams/removeTransactionDiagram.puml @@ -0,0 +1,38 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +actor User +participant "TransactionList" as TL +participant "UserInterface" as UI +participant ":AccountManager" as AM +participant ":Account" as AC +participant "EmptyArgumentException" as EAE +participant "InvalidIndexException" as IIE +User -> TL: removeTransaction [delete index] + +alt input.trim().length() < DELETE_BEGIN_INDEX + TL -> EAE: throw EmptyArgumentException +end + TL -> TL: id = parseInt(data) + alt id >= LOWER_BOUND and id < size + TL -> AM: getAccountByAccountNumber(accountNumber) + activate AM + AM --> TL: :Account + deactivate AM + + TL -> TL: get(id) + TL -> TL: remove(id) + + TL -> AC: setBalance(newBalance) + TL -> UI: + activate UI + UI --> TL: Display delete confirmation message + deactivate UI + TL --> User: Confirmation message + else + TL -> IIE: throw InvalidIndexException + end + +@enduml diff --git a/docs/images/AddAccountClassDiagram.png b/docs/images/AddAccountClassDiagram.png new file mode 100644 index 0000000000..cda8631327 Binary files /dev/null and b/docs/images/AddAccountClassDiagram.png differ diff --git a/docs/images/RemoveAccounrSequenceDiagram.png b/docs/images/RemoveAccounrSequenceDiagram.png new file mode 100644 index 0000000000..585c4f98f6 Binary files /dev/null and b/docs/images/RemoveAccounrSequenceDiagram.png differ diff --git a/docs/images/Shyam.jpg b/docs/images/Shyam.jpg new file mode 100644 index 0000000000..5932994ba9 Binary files /dev/null and b/docs/images/Shyam.jpg differ diff --git a/docs/images/TransactionListDiagram.png b/docs/images/TransactionListDiagram.png new file mode 100644 index 0000000000..325f2b37e8 Binary files /dev/null and b/docs/images/TransactionListDiagram.png differ diff --git a/docs/images/addAccountDiagram.png b/docs/images/addAccountDiagram.png new file mode 100644 index 0000000000..efe4ca825d Binary files /dev/null and b/docs/images/addAccountDiagram.png differ diff --git a/docs/images/addTransactionDiagram.png b/docs/images/addTransactionDiagram.png new file mode 100644 index 0000000000..2dc1c2ef2f Binary files /dev/null and b/docs/images/addTransactionDiagram.png differ diff --git a/docs/images/architectureDiagram.png b/docs/images/architectureDiagram.png new file mode 100644 index 0000000000..69d844cbd5 Binary files /dev/null and b/docs/images/architectureDiagram.png differ diff --git a/docs/images/insightDiagram.png b/docs/images/insightDiagram.png new file mode 100644 index 0000000000..5d047bf795 Binary files /dev/null and b/docs/images/insightDiagram.png differ diff --git a/docs/images/isaac.jpg b/docs/images/isaac.jpg new file mode 100644 index 0000000000..45f031400d Binary files /dev/null and b/docs/images/isaac.jpg differ diff --git a/docs/images/list_options.png b/docs/images/list_options.png new file mode 100644 index 0000000000..9135bf6427 Binary files /dev/null and b/docs/images/list_options.png differ diff --git a/docs/images/processEditTransactionDiagram.png b/docs/images/processEditTransactionDiagram.png new file mode 100644 index 0000000000..e191d58fc8 Binary files /dev/null and b/docs/images/processEditTransactionDiagram.png differ diff --git a/docs/images/processList.png b/docs/images/processList.png new file mode 100644 index 0000000000..9ec77df2d9 Binary files /dev/null and b/docs/images/processList.png differ diff --git a/docs/images/removeTransactionDiagram.png b/docs/images/removeTransactionDiagram.png new file mode 100644 index 0000000000..c14e3d98a3 Binary files /dev/null and b/docs/images/removeTransactionDiagram.png differ diff --git a/docs/images/successful_add_acc_feature.png b/docs/images/successful_add_acc_feature.png new file mode 100644 index 0000000000..f984a25012 Binary files /dev/null and b/docs/images/successful_add_acc_feature.png differ diff --git a/docs/images/successful_add_feature.png b/docs/images/successful_add_feature.png new file mode 100644 index 0000000000..a0cf0e5d26 Binary files /dev/null and b/docs/images/successful_add_feature.png differ diff --git a/docs/images/successful_delete_acc_feature.png b/docs/images/successful_delete_acc_feature.png new file mode 100644 index 0000000000..005873e674 Binary files /dev/null and b/docs/images/successful_delete_acc_feature.png differ diff --git a/docs/images/successful_delete_transaction.png b/docs/images/successful_delete_transaction.png new file mode 100644 index 0000000000..d68855a401 Binary files /dev/null and b/docs/images/successful_delete_transaction.png differ diff --git a/docs/images/successful_edit_acc_feature.png b/docs/images/successful_edit_acc_feature.png new file mode 100644 index 0000000000..7b2bb5c644 Binary files /dev/null and b/docs/images/successful_edit_acc_feature.png differ diff --git a/docs/images/successful_edit_transaction.png b/docs/images/successful_edit_transaction.png new file mode 100644 index 0000000000..0efcffacdf Binary files /dev/null and b/docs/images/successful_edit_transaction.png differ diff --git a/docs/images/successful_list_acc_feature.png b/docs/images/successful_list_acc_feature.png new file mode 100644 index 0000000000..25303da5b9 Binary files /dev/null and b/docs/images/successful_list_acc_feature.png differ diff --git a/docs/images/successful_list_feature.png b/docs/images/successful_list_feature.png new file mode 100644 index 0000000000..6769384e67 Binary files /dev/null and b/docs/images/successful_list_feature.png differ diff --git a/docs/images/successful_search.png b/docs/images/successful_search.png new file mode 100644 index 0000000000..fc52f6bc2e Binary files /dev/null and b/docs/images/successful_search.png differ diff --git a/docs/images/vaibhav.png b/docs/images/vaibhav.png new file mode 100644 index 0000000000..23e6062bc0 Binary files /dev/null and b/docs/images/vaibhav.png differ diff --git a/docs/images/vavinan.jpg b/docs/images/vavinan.jpg new file mode 100644 index 0000000000..c58022e1cd Binary files /dev/null and b/docs/images/vavinan.jpg differ diff --git a/docs/team/isaaceng7.md b/docs/team/isaaceng7.md new file mode 100644 index 0000000000..f844181a04 --- /dev/null +++ b/docs/team/isaaceng7.md @@ -0,0 +1,29 @@ +# Isaac Eng Hong Yeow - Project Portfolio Page + +## Overview +BudgetBuddy is a desktop financial tracker application that helps users to manage their personal +finances. It allows users to track their income and expenses across multiple accounts and provides +insights into their financial activities. It is optimized for use via a Command Line Interface (CLI) +and is written in Java, and has about 3 kLoC. + +## Summary of Contributions +Given below are my contributions to the project. +* **Code Contributed**: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=isaaceng7&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) +* **List Feature**: Added the ability to view existing transactions. + * What it does: allows users to view their existing transactions. + * Justification: this feature is key to the BudgetBuddy as users need to be aware of their finances through their transactions. + * Enhancements: users can choose between 6 different types of list to view: + 1. All Transactions + 2. Past Week Transactions + 3. Past Month Transactions + 4. Custom Date Transactions + 5. Account Transactions + 6. Category Transactions +* **Documentation**: + * User Guide: + * Added documentation for the `list` feature. + * Developer Guide: + * Added implementation details for the `list` feature. + * Added architecture diagram of BudgetBuddy. +* **Community**: + * Reported bugs and suggestions for other teams in class and PE-D. \ No newline at end of file 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/shyamkrishna33.md b/docs/team/shyamkrishna33.md new file mode 100644 index 0000000000..c11e63412f --- /dev/null +++ b/docs/team/shyamkrishna33.md @@ -0,0 +1,75 @@ +# Arun Gandhi Shyam Krishna - Project Portfolio Page + +## Overview + +BudgetBuddy is a desktop financial tracker application that helps users to manage their personal +finances. It allows users to track their income and expenses across multiple accounts and provides +insights into their financial activities. It is optimized for use via a Command Line Interface (CLI) +and is written in Java, and has about 3 kLoC. + +## Summary of Contributions + +### Code Contributed: +#### [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=Shyam&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=ShyamKrishna33&tabRepo=AY2324S2-CS2113-T15-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Documentation: + +* User Guide: + * Added documentation of feature `add`. + * Added documentation of feature `insights`. +* Developer Guide: + * Added implementation and details of `Category` feature. + * Added implementation and details of processTransaction method with sequence diagram and class diagram. +* Community: + * Reported bugs and suggestions for other teams in class and PE-D. +* Others: + * Added headers for methods in TransactionList, Transaction and DataStorage classes. + +### Feature 1 - Adding Transaction + +1. Initiated the transaction class with appropriate parameters. +2. Created the `Add` command with the basic arguments to add a transaction to a + list of available transactions. +3. Added JUnit test cases for all the important methods contributing to this feature. +4. Performed exception handling for many types of edge cases with respect to the input arguments. + +**What it does:** Allows users to add a transaction.
+**Justification:** This feature is the backbone to the BudgetBuddy as users need to be able to enter a transaction to +use the application. + +### Feature 2 - Data Storage + +1. Initiated file paths and file format to store the data of the list of transactions. +2. Created `saveTransactions` method to take in an array of transactions, convert the objects into string format + ensuring no + information is lost in the process and writing the data in the specified file paths. +3. Enabled reading of saved data back into an array of transaction objects using the method `readTransactionFile`. +4. Performed exception handling to ensure that in all scenarios the data storage works as intended without any runtime + errors. For example, ensuring that the program does not crash when the files get corrupted due to external + intervention. + +**What it does:** Stores all the transactions entered by the user to the database.
+**Justification:** This feature is very important as it allows the user to track all the transactions in previous +sessions as it would be meaningless to not have a data storage. + +### Feature 3 - Insights + +1. Used XChart library to display pie-charts on the available data. +2. Segregated all the available transactions into income and expense type and calculated total amount for each + category in each type. Displayed the proportions of transactions in each category for each type. + +**What it does:** Displays 2 pie-charts, one for each type of transaction, which shows the proportion +of each category in all the transactions.
+**Justification:** This feature is useful since it allows the user to visualize the distribution of +his/her income/expense among the available categories. + +### Exception Handling + +* Performed overall exception handling for many peculiar cases like user deleting the storage when program + is running, user entering special characters in command syntax, user force quitting the program etc. +* Also added JUnit test cases for many crucial methods to ensure robustness. + +### General Contribution + +* Took part regularly in code reviews and team meetings. +* Reviewed around 20 Pull Requests. diff --git a/docs/team/vavinan.md b/docs/team/vavinan.md new file mode 100644 index 0000000000..4249b4608e --- /dev/null +++ b/docs/team/vavinan.md @@ -0,0 +1,73 @@ +# Jeevanandham Vavinan - Project Portfolio Page + +### Overview +BudgetBuddy is a desktop financial tracker application that helps users to manage their personal finances. It allows +users to track their income and expenses across multiple accounts and provides insights into their financial activities. +It is optimized for use via a Command Line Interface (CLI) and is written in Java, and has about 3 kLoC. + +### Summary of Contributions +##### Code Contributed: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=vavinan&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) +##### Documentation: + * User Guide: + * Added documentation for the feature `delete`. + * Added documentation for the feature `edit`. + * Added documentation for the feature `search` + * Developer Guide: + * Added implementation details for the feature `delete`. + * Added implementation details for the feature `edit`. + * Added implementation details for the feature `insights`. + * Added implementation details for the feature `search`. + + * Community: + * Reported bugs and suggestions for other teams in class and PE-D. + + * Others: + * Added headers for UserInterface class and Parser class +
+ +### Feature 1 - Deleting Transaction + +1. Created the `removeTransaction` method to handle deletion of a transaction based on its index in the list. +2. Facilitated with the command `delete` followed by the index ID of the transaction to be deleted. +3. Implemented error handling for various edge cases, including empty input, invalid index, and out-of-bound + index. +4. Removed the transaction from the transaction list and updated the account balance. Then the user will + get the confirmation message about the process. + +**What it does:** Allows users to delete the transaction.
+**Justification:** This feature is key to the BudgetBuddy as users need to be able to delete the transactions +which has errors or added by mistake. + +### Feature 2 - Editing Transaction + +1. Implemented the `processEditTransaction` method to facilitate editing of a transaction based on its index. +2. Facilitated by the command `edit` followed by the index ID of the transaction to be edited. +3. Validated user input for the index and transaction data, handling exceptions such as empty input and + non-integer index. +4. Prompted the user to provide updated information for the transaction and validated each piece of data, + and throw appropriate exceptions associated with the input being given. +5. Updated the transaction list with the edited transaction and printed a confirmation message. + +**What it does:** Allows users to edit the transaction.
+**Justification:** This feature is key to the BudgetBuddy as users need to be able to edit the transactions +which has been added with some mistakes and need to be updated.
+**Highlights:** Instead of expecting the user to type long command this feature prompts the user to input +value for each data + +### Feature 3 - Search Transaction + +1. Implemented the `searchTransaction` method to facilitate searching of transactions using a keyword + based on description, date, category or amount. +2. Facilitated by the command `search` followed by the keyword. +3. If the keyword is missing, exception is thrown and the suer will be alerted +4. The keyword is used to search from the list and the matching results will be shows as a table along + with its true **index ID**. If there is no matching transactions, then the user will be notified that there + is no matching transactions. + +**What it does:** Allows users to search for transactions
+**Justification:** This feature is key to the BudgetBuddy as if the transaction history is too long and +a user wants to delete or edit transaction. Then this search functionality will be helpful as the user can +enter a keyword to search for it to get the true index ID of the transaction. Then that ID can be used in +delete or edit command. + + diff --git a/docs/team/vibes-863.md b/docs/team/vibes-863.md new file mode 100644 index 0000000000..25678d4530 --- /dev/null +++ b/docs/team/vibes-863.md @@ -0,0 +1,76 @@ +# Vaibhav Dileep Pillai's Project Portfolio Page + +## Overview + +BudgetBuddy is a desktop financial tracker application that helps users to manage their personal finances. It allows +users to track their income and expenses across multiple accounts and provides insights into their financial activities. +It is optimized for use via a Command Line Interface (CLI) and is written in Java, and has more than 4 kLoC. + +## Summary of Contributions + +Given below is a summary of my contributions to the project. + +### Code Contributed: + +#### [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=vibes-863&breakdown=true) + +### Features implemented: + +#### 1. Feature: Account Addition + +- **What it does:** Enables users to create new financial accounts within the application. +- **Justification:** Fundamental for financial tracking, this feature allows users to start managing their finances by + setting up accounts with a name and initial balance. +- **Highlights:** The implementation ensures that each account has a unique account number and integrates seamlessly + with the rest of the system to reflect updates across transactions and balances. + +#### 2. Feature: Account Deletion + +- **What it does:** Provides the functionality to remove existing accounts from the application. +- **Justification:** Essential for maintaining current and relevant financial data, this feature allows users to delete + accounts that are obsolete or were created in error. +- **Highlights:** Includes checks to prevent deletion of the last remaining account, thereby ensuring there is always at + least one account active for transaction logging. + +#### 3. Feature: Account Listing + +- **What it does:** Displays a comprehensive list of all user accounts with details such as account number, name, and + current balance. +- **Justification:** Users need a quick overview of all their accounts to make informed financial decisions. This + feature supports financial awareness and planning. +- **Highlights:** The list is dynamically updated, reflecting real-time changes in account details and balances. + +#### 4. Feature: Account Editing + +- **What it does:** Allows users to modify the details of existing accounts, such as changing the account name. +- **Justification:** Flexibility in updating account information is crucial as it evolves with the user's financial + landscape. +- **Highlights:** This feature ensures data integrity while allowing modifications, enhancing user experience by + allowing personalization of account information. + +#### 5. Feature: Account Data Storage + +- **What it does:** Ensures that account data is saved to a file system and can be reloaded each time the application + starts, preserving user data between sessions. +- **Justification:** Vital for long-term financial tracking, users can shut down and restart the application without + losing their data, enabling continuous financial management. +- **Highlights:** Implements robust error handling to manage file integrity issues and provides a seamless user + experience by automatically handling data storage and retrieval. + +### Contributions to User Guide: + +- Updated documentation for the features `delete-acc`, `list-acc`, and `edit-acc`. +- Formatted the user guide to improve readability and consistency. + +### Contributions to Developer Guide: + +- Added implementation details for the features `Add Account` and `Remove Account`. +- Formatted the developer guide to improve readability and consistency. + +### General Contributions: + +- Participated in team meetings and discussions to plan and implement features. +- Managed `v1.0` release on GitHub. +- Reviewed around 20 plus PRs and provided feedback to teammates. +- + diff --git a/src/main/java/budgetbuddy/BudgetBuddy.java b/src/main/java/budgetbuddy/BudgetBuddy.java new file mode 100644 index 0000000000..2c661f446f --- /dev/null +++ b/src/main/java/budgetbuddy/BudgetBuddy.java @@ -0,0 +1,174 @@ +package budgetbuddy; + +import budgetbuddy.account.Account; +import budgetbuddy.account.AccountManager; +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidAddTransactionSyntax; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.exceptions.InvalidEditTransactionData; +import budgetbuddy.exceptions.InvalidIndexException; +import budgetbuddy.exceptions.InvalidTransactionTypeException; +import budgetbuddy.insights.Insight; +import budgetbuddy.parser.Parser; +import budgetbuddy.storage.DataStorage; +import budgetbuddy.transaction.TransactionList; +import budgetbuddy.ui.UserInterface; + +import java.io.File; +import java.io.IOException; + +import java.util.Scanner; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +public class BudgetBuddy { + public static final int LIST_LENGTH = 5; + public static final String BYE = "bye"; + public static final String LIST = "list"; + public static final String DELETE = "delete"; + public static final String ADD = "add"; + public static final String EDIT = "edit"; + public static final String HELP = "help"; + public static final String ADD_ACC = "add-acc"; + public static final String INSIGHTS = "insights"; + public static final String LIST_ACC = "list-acc"; + public static final String DELETE_ACC = "delete-acc"; + public static final String EDIT_ACC = "edit-acc"; + public static final String SEARCH = "search"; + public static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + private final AccountManager accountManager; + private final TransactionList transactions; + + /** + * Creates a BudgetBuddy object with the account manager and transaction list. + */ + public BudgetBuddy() { + DataStorage dataStorage = new DataStorage(); + this.accountManager = dataStorage.loadAccounts(); + this.transactions = dataStorage.loadTransactions(accountManager.getExistingAccountNumbers()); + } + + /** + * Sets up the logger for the BudgetBuddy application. + */ + private static void setupLogger() { + LogManager.getLogManager().reset(); + LOGGER.setLevel(java.util.logging.Level.ALL); + try { + File logsDir = new File("logs"); + if (!logsDir.exists()) { + logsDir.mkdir(); + } + FileHandler fh = new FileHandler("logs/budgetBuggyLog.log"); + fh.setFormatter(new SimpleFormatter()); + fh.setLevel(Level.INFO); + LOGGER.addHandler(fh); + } catch (IOException e) { + UserInterface.printLoggerSetupError(); + } + } + + /** + * Main entry-point for the java.BudgetBuddy application. + */ + public static void main(String[] args) { + setupLogger(); + new BudgetBuddy().run(); + } + + /** + * Runs the BudgetBuddy application. + */ + public void run() { + Scanner in = UserInterface.in; + String logo = "BUDGET BUDDY"; + + System.out.println("Hello from\n" + logo); + System.out.println("What can I do for you?"); + + + boolean isRunning = true; + + while (isRunning) { + String input = in.nextLine(); + try { + if (input.contains(",")){ + throw new InvalidArgumentSyntaxException("Input cannot contain ',' comma."); + } + switch (input.split(" ")[0].toLowerCase()) { + case BYE: + Insight.closeInsightFrames(); + UserInterface.printGoodBye(); + isRunning = false; + break; + case LIST: + transactions.processList(accountManager.getAccounts(), accountManager); + break; + case DELETE: + transactions.removeTransaction(input, accountManager); + break; + case ADD: + int accountNumber = Parser.parseAccountNumber(input); + Account account = accountManager.getAccountByAccountNumber(accountNumber); + transactions.processTransaction(input, account); + break; + case EDIT: + transactions.processEditTransaction(input, accountManager); + break; + case HELP: + transactions.helpWithUserCommands(input); + break; + case ADD_ACC: + accountManager.processAddAccount(input); + break; + case INSIGHTS: + transactions.displayInsights(); + break; + case LIST_ACC: + UserInterface.printListOfAccounts(accountManager.getAccounts()); + break; + case DELETE_ACC: + accountManager.removeAccount(input, transactions); + break; + case EDIT_ACC: + accountManager.processEditAccount(input); + break; + case SEARCH: + transactions.searchTransactions(input); + break; + default: + UserInterface.printNoCommandExists(); + } + transactions.saveTransactionList(); + accountManager.saveAccounts(); + + } catch (InvalidAddTransactionSyntax e) { + UserInterface.printInvalidAddSyntax(e.getMessage()); + } catch (NumberFormatException e) { + UserInterface.printNumberFormatError(e.getMessage()); + } catch (InvalidTransactionTypeException e) { + UserInterface.printTransactionTypeError(e.getMessage()); + } catch (EmptyArgumentException e) { + UserInterface.printEmptyArgumentError(e.getMessage()); + } catch (InvalidIndexException e) { + UserInterface.printInvalidIndex("Given index id is out of bound", + Integer.parseInt(e.getMessage())); + } catch (IndexOutOfBoundsException ignored) { + UserInterface.printInvalidInput("Please check your command syntax"); + } catch (InvalidEditTransactionData e) { + UserInterface.printInvalidInput(e.getMessage()); + } catch (InvalidArgumentSyntaxException e) { + UserInterface.printInvalidArgumentSyntax(e.getMessage()); + } catch (InvalidCategoryException e) { + UserInterface.printInvalidCategoryError(); + } catch (Exception e) { + UserInterface.printExceptionErrorMessage(e.getMessage()); + } + } + + } +} diff --git a/src/main/java/budgetbuddy/account/Account.java b/src/main/java/budgetbuddy/account/Account.java new file mode 100644 index 0000000000..67a12832ce --- /dev/null +++ b/src/main/java/budgetbuddy/account/Account.java @@ -0,0 +1,102 @@ +package budgetbuddy.account; + +import java.util.logging.Logger; + +/** + * Represents an account in the budget buddy system. + */ +public class Account { + public static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + private final int accountNumber; + private String name; + private double balance; + + /** + * Creates an account with the given account number, and default name and balance. + * + * @param accountNumber the account number + */ + public Account(int accountNumber) { + assert accountNumber > 0 : "Account number must be positive"; + this.accountNumber = accountNumber; + this.name = ""; + this.balance = 0.00; + LOGGER.info("Account created with default name and balance"); + } + + /** + * Creates an account with the given account number, name and balance. + * + * @param accountNumber the account number + * @param name the name + * @param balance the balance + */ + public Account(int accountNumber, String name, double balance) { + assert accountNumber > 0 : "Account number must be positive"; + assert name != null : "Name cannot be null"; + this.accountNumber = accountNumber; + this.name = name; + this.balance = balance; + LOGGER.info("Account created with specified name and balance"); + } + + /** + * Returns the current balance of the account. + * + * @return the current balance + */ + public double getBalance() { + return balance; + } + + /** + * Sets the balance of the account to the given amount. + * + * @param balance the new balance + */ + public void setBalance(double balance) { + this.balance = balance; + LOGGER.info("Account balance updated"); + } + + /** + * Returns the account number. + * + * @return the account number + */ + public int getAccountNumber() { + return accountNumber; + } + + /** + * Returns the name of the account. + * + * @return the name of the account + */ + public String getName() { + return name; + } + + /** + * Sets the name of the account to the given name. + * + * @param name the new name + */ + public void setName(String name) { + assert name != null : "Name cannot be null"; + this.name = name; + LOGGER.info("Account name updated"); + } + + /** + * Returns a string representation of the account, including the account number, name, and balance. + * + * @return a string representation of the account + */ + @Override + public String toString() { + return (" Account Number: " + getAccountNumber() + " | " + + " Name: " + getName() + " | " + + " Balance: " + getBalance()); + } +} diff --git a/src/main/java/budgetbuddy/account/AccountManager.java b/src/main/java/budgetbuddy/account/AccountManager.java new file mode 100644 index 0000000000..cfc4da855c --- /dev/null +++ b/src/main/java/budgetbuddy/account/AccountManager.java @@ -0,0 +1,217 @@ +package budgetbuddy.account; + +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.exceptions.InvalidIndexException; +import budgetbuddy.parser.Parser; +import budgetbuddy.storage.DataStorage; +import budgetbuddy.transaction.TransactionList; +import budgetbuddy.transaction.type.Transaction; +import budgetbuddy.ui.UserInterface; + +import java.util.ArrayList; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manages the accounts in the budget buddy system. + */ +public class AccountManager { + public static final int INDEX_OFFSET = 1; + public static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + private final DataStorage dataStorage = new DataStorage(); + private final ArrayList accounts; + private final ArrayList existingAccountNumbers; + + + /** + * Creates an AccountManager with empty account and account number lists. + */ + public AccountManager() { + this.accounts = new ArrayList<>(); + this.existingAccountNumbers = new ArrayList<>(); + LOGGER.log(Level.INFO, "AccountManager created with empty account and account number lists"); + } + + /** + * Creates an AccountManager with the given account and account number lists. + * + * @param accounts the list of accounts + * @param existingAccountNumbers the list of existing account numbers + */ + public AccountManager(ArrayList accounts, ArrayList existingAccountNumbers) { + assert accounts != null : "Accounts list cannot be null"; + assert existingAccountNumbers != null : "Existing account numbers list cannot be null"; + this.accounts = accounts; + this.existingAccountNumbers = existingAccountNumbers; + LOGGER.log(Level.INFO, "AccountManager created with specified account and account number lists"); + } + + /** + * Adds an account with the given name and initial balance. + * + * @param name the name of the account + * @param initialBalance the initial balance of the account + */ + public void addAccount(String name, double initialBalance) { + assert name != null : "Name cannot be null"; + int newAccountNumber = generateAccountNumber(); + accounts.add(new Account(newAccountNumber, name, initialBalance)); + existingAccountNumbers.add(newAccountNumber); + LOGGER.log(Level.INFO, "Account added"); + } + + /** + * Generates a unique four-digit account number. + * + * @return the generated account number + */ + public int generateAccountNumber() { + Random random = new Random(); + boolean noMatchFound = true; + int fourDigitNumber; + do { + fourDigitNumber = 1000 + random.nextInt(9000); + for (int accountNumber : existingAccountNumbers) { + if (accountNumber == fourDigitNumber) { + noMatchFound = false; + LOGGER.log(Level.WARNING, "Account number already exists. Generating new account number."); + break; + } + } + } while (!noMatchFound); + + LOGGER.log(Level.INFO, "Account number generated"); + return fourDigitNumber; + } + + /** + * Processes the addition of an account from the given input. + * + * @param input the input string + * @throws InvalidArgumentSyntaxException if the input syntax is invalid + * @throws NumberFormatException if the input contains an invalid number + * @throws EmptyArgumentException if the input is empty + */ + public void processAddAccount(String input) + throws InvalidArgumentSyntaxException, NumberFormatException, EmptyArgumentException { + assert input != null : "Input cannot be null"; + LOGGER.log(Level.INFO, "Processing add account command"); + String[] arguments = {"/n/", "/$/"}; + for (String argument : arguments) { + if (!input.contains(argument)) { + LOGGER.log(Level.WARNING, "Invalid add account syntax."); + throw new InvalidArgumentSyntaxException("Invalid add account syntax."); + } + } + String[] parsedData = Parser.parseAddAccount(input); + addAccount(parsedData[0], Double.parseDouble(parsedData[1])); + UserInterface.printAddAccountMessage(getAccount(accounts.size() - INDEX_OFFSET).toString()); + LOGGER.log(Level.INFO, "Account added successfully"); + } + + /** + * Removes an account with the given input and updates the transaction list. + * + * @param input the input string + * @param transactions the transaction list + * @throws NumberFormatException if the input contains an invalid number + * @throws InvalidArgumentSyntaxException if the input syntax is invalid + * @throws EmptyArgumentException if the input is empty + * @throws InvalidIndexException if the input contains an invalid index + */ + public void removeAccount(String input, TransactionList transactions) + throws NumberFormatException, InvalidArgumentSyntaxException, EmptyArgumentException, + InvalidIndexException { + assert input != null : "Input cannot be null"; + assert transactions != null : "Transactions cannot be null"; + LOGGER.log(Level.INFO, "Processing remove account command"); + int accountNumber = Parser.parseRemoveAccount(input); + Account accountRemoved = getAccountByAccountNumber(accountNumber); + if (accounts.size() == 1) { + UserInterface.printCannotDeleteLastAccountMessage(); + LOGGER.log(Level.WARNING, "Cannot delete last account."); + return; + } + accounts.remove(accountRemoved); + existingAccountNumbers.remove(Integer.valueOf(accountNumber)); + ArrayList transactionsRemoved = transactions.removeTransactionsByAccountNumber(accountNumber); + UserInterface.printDeleteAccountMessage(accountRemoved.toString(), transactionsRemoved); + LOGGER.log(Level.INFO, "Account removed successfully"); + } + + /** + * Returns the account with the given account ID. + * + * @param accountId the account ID + * @return the account + */ + public Account getAccount(int accountId) { + assert accountId >= 0 : "Account ID cannot be negative"; + return accounts.get(accountId); + } + + /** + * Returns the account with the given account number. + * + * @param accountNumber the account number + * @return the account + * @throws IllegalArgumentException if the account is not found + */ + public Account getAccountByAccountNumber(int accountNumber) { + assert accountNumber > 0 : "Account number must be positive"; + for (Account account : accounts) { + if (account.getAccountNumber() == accountNumber) { + return account; + } + } + LOGGER.log(Level.WARNING, "Account not found."); + throw new IllegalArgumentException("Account not found."); + } + + /** + * Returns the list of accounts. + * + * @return the list of accounts + */ + public ArrayList getAccounts() { + return accounts; + } + + /** + * Processes the editing of an account from the given input. + * + * @param input the input string + * @throws EmptyArgumentException if the input is empty + * @throws IllegalArgumentException if the input is invalid + */ + public void processEditAccount(String input) throws EmptyArgumentException, IllegalArgumentException, + InvalidArgumentSyntaxException { + assert input != null : "Input cannot be null"; + LOGGER.log(Level.INFO, "Processing edit account command"); + int accountNumber = Parser.parseEditAccount(input); + Account account = getAccountByAccountNumber(accountNumber); + String newName = UserInterface.getNewAccountName(account.toString()); + account.setName(newName); + UserInterface.printUpdatedAccount(account.toString()); + LOGGER.log(Level.INFO, "Account edited successfully"); + } + + /** + * Saves the accounts to the data storage. + */ + public void saveAccounts() { + assert accounts != null : "Accounts list cannot be null"; + dataStorage.saveAccounts(accounts); + } + + /** + * Returns the list of existing account numbers. + * + * @return the list of existing account numbers + */ + public ArrayList getExistingAccountNumbers() { + return existingAccountNumbers; + } +} diff --git a/src/main/java/budgetbuddy/categories/Category.java b/src/main/java/budgetbuddy/categories/Category.java new file mode 100644 index 0000000000..6537f0e95b --- /dev/null +++ b/src/main/java/budgetbuddy/categories/Category.java @@ -0,0 +1,40 @@ +package budgetbuddy.categories; + +import budgetbuddy.exceptions.InvalidCategoryException; + +public enum Category { + DINING(1, "Dining"), + GROCERIES(2, "Groceries"), + UTILITIES(3, "Utilities"), + TRANSPORTATION(4, "Transportation"), + HEALTHCARE(5, "Healthcare"), + ENTERTAINMENT(6, "Entertainment"), + RENT(7, "Rent"), + SALARY(8, "Salary"), + OTHERS(9, "Others"); + + private final int categoryNum; + private final String categoryName; + + Category(int categoryNum, String categoryName) { + this.categoryNum = categoryNum; + this.categoryName = categoryName; + } + + public static Category fromNumber(int number) throws InvalidCategoryException { + for (Category category : Category.values()) { + if (category.categoryNum == number) { + return category; + } + } + throw new InvalidCategoryException("Category index out of bounds"); + } + + public String getCategoryName() { + return categoryName; + } + + public int getCategoryNum() { + return categoryNum; + } +} diff --git a/src/main/java/budgetbuddy/exceptions/EmptyArgumentException.java b/src/main/java/budgetbuddy/exceptions/EmptyArgumentException.java new file mode 100644 index 0000000000..9d2582a60c --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/EmptyArgumentException.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class EmptyArgumentException extends Exception { + public EmptyArgumentException(String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/exceptions/FileCorruptedException.java b/src/main/java/budgetbuddy/exceptions/FileCorruptedException.java new file mode 100644 index 0000000000..8925be8854 --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/FileCorruptedException.java @@ -0,0 +1,9 @@ +package budgetbuddy.exceptions; + +//@@author ShyamKrishna33 +public class FileCorruptedException extends Exception { + public FileCorruptedException(String message) { + super(message); + } +} +//@@author diff --git a/src/main/java/budgetbuddy/exceptions/InvalidAddTransactionSyntax.java b/src/main/java/budgetbuddy/exceptions/InvalidAddTransactionSyntax.java new file mode 100644 index 0000000000..1c870a3211 --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/InvalidAddTransactionSyntax.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class InvalidAddTransactionSyntax extends Exception{ + public InvalidAddTransactionSyntax(String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/exceptions/InvalidArgumentSyntaxException.java b/src/main/java/budgetbuddy/exceptions/InvalidArgumentSyntaxException.java new file mode 100644 index 0000000000..127ffff902 --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/InvalidArgumentSyntaxException.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class InvalidArgumentSyntaxException extends Exception{ + public InvalidArgumentSyntaxException (String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/exceptions/InvalidCategoryException.java b/src/main/java/budgetbuddy/exceptions/InvalidCategoryException.java new file mode 100644 index 0000000000..4568b7bf3a --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/InvalidCategoryException.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class InvalidCategoryException extends Exception{ + public InvalidCategoryException(String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/exceptions/InvalidEditTransactionData.java b/src/main/java/budgetbuddy/exceptions/InvalidEditTransactionData.java new file mode 100644 index 0000000000..3f1520c8ac --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/InvalidEditTransactionData.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class InvalidEditTransactionData extends Exception{ + public InvalidEditTransactionData(String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/exceptions/InvalidIndexException.java b/src/main/java/budgetbuddy/exceptions/InvalidIndexException.java new file mode 100644 index 0000000000..fde96f40bd --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/InvalidIndexException.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class InvalidIndexException extends Exception { + public InvalidIndexException (String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/exceptions/InvalidTransactionTypeException.java b/src/main/java/budgetbuddy/exceptions/InvalidTransactionTypeException.java new file mode 100644 index 0000000000..40f6960432 --- /dev/null +++ b/src/main/java/budgetbuddy/exceptions/InvalidTransactionTypeException.java @@ -0,0 +1,7 @@ +package budgetbuddy.exceptions; + +public class InvalidTransactionTypeException extends Exception{ + public InvalidTransactionTypeException (String message) { + super(message); + } +} diff --git a/src/main/java/budgetbuddy/insights/Insight.java b/src/main/java/budgetbuddy/insights/Insight.java new file mode 100644 index 0000000000..198ffc2d7d --- /dev/null +++ b/src/main/java/budgetbuddy/insights/Insight.java @@ -0,0 +1,120 @@ +package budgetbuddy.insights; + +import budgetbuddy.categories.Category; +import budgetbuddy.transaction.type.Expense; +import budgetbuddy.transaction.type.Income; +import budgetbuddy.transaction.type.Transaction; +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; +import org.knowm.xchart.XChartPanel; +import org.knowm.xchart.style.PieStyler; + + +import javax.swing.JFrame; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.util.ArrayList; +import java.awt.Window; + +import static java.lang.Math.abs; + +public class Insight { + //@@author ShyamKrishna33 + public static void displayCategoryInsight(ArrayList transactionArrayList) { + Category[] categoryArray = Category.values(); + Double[] expenseArray = new Double[categoryArray.length]; + Double[] incomeArray = new Double[categoryArray.length]; + + for (int i = 0; i < categoryArray.length; i++) { + expenseArray[i] = 0.0; + incomeArray[i] = 0.0; + } + for(Transaction t : transactionArrayList){ + Category category = t.getCategory(); + int index = indexOf(categoryArray, category); + if (t instanceof Expense) { + expenseArray[index] += abs(t.getAmount()); + } else if (t instanceof Income){ + incomeArray[index] += abs(t.getAmount()); + } + } + displayPieChart(categoryArray, incomeArray, expenseArray); + } + + //@@author + private static void displayPieChart(Category[] categoryArray, Double[] incomeArray, Double[] expenseArray) { + JFrame incomeFrame = new JFrame("Income Insights"); + incomeFrame.setLayout(new BorderLayout()); + JFrame expenseFrame = new JFrame("Expense Insights"); + expenseFrame.setLayout(new BorderLayout()); + + JPanel incomePanel = new JPanel(); + incomeFrame.add(incomePanel, BorderLayout.CENTER); + JPanel expensePanel = new JPanel(); + expenseFrame.add(expensePanel, BorderLayout.CENTER); + + PieChart incomeChart = new PieChartBuilder().width(800).height(600).title("Income Divide").build(); + + // Customize Chart + incomeChart.getStyler().setCircular(true); + incomeChart.getStyler().setLegendVisible(true); + incomeChart.getStyler().setAnnotationType(PieStyler.AnnotationType.LabelAndPercentage); + + for (int i = 0; i < categoryArray.length; i++) { + if (incomeArray[i] != 0) { + incomeChart.addSeries(categoryArray[i].getCategoryName(), incomeArray[i]); + } + } + + // Show it + incomePanel.add(new XChartPanel<>(incomeChart)); + incomeFrame.pack(); + incomeFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + incomeFrame.setVisible(true); + + PieChart expenseChart = new PieChartBuilder().width(800).height(600).title("Expense Divide").build(); + + // Customize Chart + expenseChart.getStyler().setCircular(true); + expenseChart.getStyler().setLegendVisible(true); + expenseChart.getStyler().setAnnotationType(PieStyler.AnnotationType.LabelAndPercentage); + + for (int i = 0; i < categoryArray.length; i++) { + if (expenseArray[i] != 0) { + expenseChart.addSeries(categoryArray[i].getCategoryName(), expenseArray[i]); + } + } + + // Show it + expensePanel.add(new XChartPanel<>(expenseChart)); + expenseFrame.pack(); + expenseFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + expenseFrame.setVisible(true); + + } + + //@@author ShyamKrishna33 + private static int indexOf(Category[] array, Category target) { + for (int i = 0; i < array.length; i++) { + if (array[i].equals(target)) { + return i; + } + } + return -1; + } + + //@@author Vavinan + public static void closeInsightFrames() { + // Close any open insight frames here + for (Window window : Window.getWindows()) { + if (window instanceof JFrame) { + String title = ((JFrame) window).getTitle(); + if (title != null && (title.equals("Income Insights") || title.equals("Expense Insights"))) { + window.dispose(); + } + } + } + } + //@@author + +} diff --git a/src/main/java/budgetbuddy/parser/Parser.java b/src/main/java/budgetbuddy/parser/Parser.java new file mode 100644 index 0000000000..c0672f7370 --- /dev/null +++ b/src/main/java/budgetbuddy/parser/Parser.java @@ -0,0 +1,255 @@ +package budgetbuddy.parser; + +import budgetbuddy.account.Account; +import budgetbuddy.categories.Category; + +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidAddTransactionSyntax; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.exceptions.InvalidEditTransactionData; +import budgetbuddy.exceptions.InvalidTransactionTypeException; +import budgetbuddy.transaction.TransactionList; +import budgetbuddy.transaction.type.Expense; +import budgetbuddy.transaction.type.Income; +import budgetbuddy.transaction.type.Transaction; +import budgetbuddy.ui.UserInterface; + +import java.util.logging.Logger; +import java.util.logging.Level; + +/** + * Parses the user input into data that is easily understandable by other classes and methods. + */ +public class Parser { + + public static final int ADD_COMMAND_INDEX = 3; + public static final int HELP_BEGIN_INDEX = 4; + public static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + private static final int ADD_ACC_COMMAND_INDEX = 7; + + /** + * The function `parseAccountNumber` extracts and returns an account number from a given input + * string following a specific syntax. + * + * @param input The input string that contains the account number to be parsed. + * @return The method is returning an integer value, which is the account number parsed from the input string. + */ + public static int parseAccountNumber(String input) throws InvalidArgumentSyntaxException { + String data = input.substring(ADD_COMMAND_INDEX + 1); + String[] parseData = data.split("/"); + for (int i = 0; i < parseData.length - 1; i++) { + if (parseData[i].trim().equals("a")) { + return Integer.parseInt(parseData[i + 1].trim()); + } + } + throw new InvalidArgumentSyntaxException("Invalid add syntax."); + } + + /** + * The function `parseUserInputToTransaction` takes user input, parses it to create a transaction + * object (either income or expense), and handles various exceptions related to invalid input. + * + * @param input takes a user input that contain transaction details in a specific format. + * @param account The `account` parameter in this method represents the account to which the transaction belongs. + * @return The method `parseUserInputToTransaction` is returning a `Transaction` object, which can + * be either an `Income` or an `Expense` object based on the type provided in the input. + */ + + public Transaction parseUserInputToTransaction(String input, Account account) + throws InvalidTransactionTypeException, NumberFormatException, + EmptyArgumentException, InvalidCategoryException, InvalidAddTransactionSyntax { + LOGGER.log(Level.INFO, "Parsing user input."); + + String data = input.substring(ADD_COMMAND_INDEX + 1); + String[] parseData = data.split("/"); + String type = null; + String description = null; + String date = null; + String amount = null; + int category = -1; + for (int i = 0; i < parseData.length - 1; i++) { + switch (parseData[i].trim()) { + case "t": + type = parseData[i + 1].trim(); + break; + case "n": + description = parseData[i + 1].trim(); + break; + case "$": + if (TransactionList.isNotDouble(parseData[i + 1].trim())) { + LOGGER.log(Level.SEVERE, "Number format incorrect"); + throw new NumberFormatException(parseData[i + 1].trim()); + } else { + amount = parseData[i + 1].trim(); + break; + } + case "d": + date = parseData[i + 1].trim(); + break; + case "c": + category = Integer.parseInt(parseData[i + 1].trim()); + break; + default: + break; + } + } + assert amount != null; + assert type != null; + + if (category == -1) { + LOGGER.log(Level.INFO, "Category not entered. Prompting for category."); + UserInterface.listCategories(); + category = UserInterface.getCategoryNum(); + } + + if (category < 1 || category > 9) { + LOGGER.log(Level.SEVERE, "Category index out of bounds"); + throw new InvalidCategoryException("Category Index out of bounds"); + } + + if (Double.parseDouble(amount) < 0) { + LOGGER.log(Level.SEVERE, "Received negative amount."); + throw new InvalidAddTransactionSyntax("Amount cannot be negative"); + } + + if (description.trim().isEmpty() || type.trim().isEmpty()) { + LOGGER.log(Level.SEVERE, "One or more arguments are empty"); + throw new EmptyArgumentException("data for the arguments "); + } else if (type.equalsIgnoreCase("income")) { + Income income = new Income(account.getAccountNumber(), account.getName(), description, + Double.parseDouble(amount), date, account); + income.setCategory(Category.fromNumber(category)); + LOGGER.log(Level.INFO, "Successfully created transaction object"); + return income; + } else if (type.equalsIgnoreCase("expense")) { + Expense expense = new Expense(account.getAccountNumber(), account.getName(), description, + Double.parseDouble(amount), date, account); + expense.setCategory(Category.fromNumber(category)); + LOGGER.log(Level.INFO, "Successfully created transaction object"); + return expense; + } else { + LOGGER.log(Level.SEVERE, "Received invalid transaction type"); + throw new InvalidTransactionTypeException(type); + } + } + + //@@author Vavinan + + /** + * The `parseEditTransaction` function in Java parses a new transaction string and creates either + * an Income or Expense object based on the transaction type, validating the category number and + * throwing exceptions for invalid data. + * + * @param newTransaction The `newTransaction` string is expected to be in a specific format + * @param account The `account` parameter in the `parseEditTransaction` method represents the + * account to which the transaction belongs. + * @return The `parseEditTransaction` method is returning a `Transaction` object, which can be either + * an `Income` or `Expense` object based on the type provided in the `newTransaction` string. + */ + + public Transaction parseEditTransaction(String newTransaction, Account account) throws InvalidEditTransactionData, + InvalidCategoryException { + String[] parts = newTransaction.split(" \\| "); + + String type = parts[0].trim(); + String description = parts[1].trim(); + String date = parts[2].trim(); + String amount = parts[3].trim(); + String category = parts[4].trim(); + int categoryValue = Integer.parseInt(category); + if (categoryValue <= 0 || categoryValue > 9) { + throw new InvalidEditTransactionData("Choose category number from the list 1-9"); + } + if (type.equalsIgnoreCase("income")) { + Income income = new Income(account.getAccountNumber(), account.getName(), description, + Double.parseDouble(amount), date, account); + income.setCategory(Category.fromNumber(categoryValue)); + return income; + } else if (type.equalsIgnoreCase("expense")) { + Expense expense = new Expense(account.getAccountNumber(), account.getName(), description, + Double.parseDouble(amount), date, account); + expense.setCategory(Category.fromNumber(categoryValue)); + return expense; + } else { + throw new InvalidEditTransactionData(" One or more data is wrong. "); + } + } + + public String parseHelpCommand(String input) { + return input.substring(HELP_BEGIN_INDEX).trim(); + } + //@@author + + /** + * The `parseAddAccount` function in Java parses the input and returns the account name and + * balance of the new account that is to added. + * + * @param input It contains details about account name and initial balance + * @return A string array that contains the account name and initial balance + */ + public static String[] parseAddAccount(String input) throws NumberFormatException, EmptyArgumentException { + String data = input.substring(ADD_ACC_COMMAND_INDEX + 1).trim(); + String[] parseData = data.split("/"); + String name = null; + String initialBalance = null; + for (int i = 0; i < parseData.length - 1; i++) { + if (parseData[i].trim().equals("n")) { + name = parseData[i + 1].trim(); + } else if (parseData[i].trim().equals("$")) { + initialBalance = parseData[i + 1].trim(); + } + } + + if (name == null || name.isEmpty()) { + throw new EmptyArgumentException("name "); + } + + if (initialBalance == null) { + throw new EmptyArgumentException("initial balance "); + } + + if (TransactionList.isNotDouble(initialBalance)) { + throw new NumberFormatException(initialBalance); + } + + return new String[]{name, initialBalance}; + } + + /** + * The `parseRemoveAccount` function in Java parses the input and returns the account + * number that is to be deleted + * + * @param input It contains details about account number that is to be deleted + * @return A integer value representing the account number + */ + public static int parseRemoveAccount(String input) + throws NumberFormatException, EmptyArgumentException { + if (input.trim().length() < 11) { + throw new EmptyArgumentException("delete-acc index "); + } + String data = input.substring(11).trim(); + if (TransactionList.isNotInteger(data)) { + throw new NumberFormatException(data); + } + return Integer.parseInt(data); + } + + /** + * The `parseEditAccount` function in Java parses the input and returns the account + * number that is to be edited + * + * @param input It contains details about account number that is to be edited + * @return A integer value representing the account number + */ + public static int parseEditAccount(String input) throws EmptyArgumentException { + if (input.trim().length() < 9) { + throw new EmptyArgumentException("edit-acc index "); + } + String data = input.substring(9).trim(); + if (TransactionList.isNotInteger(data)) { + throw new NumberFormatException(data); + } + return Integer.parseInt(data); + } +} diff --git a/src/main/java/budgetbuddy/storage/DataStorage.java b/src/main/java/budgetbuddy/storage/DataStorage.java new file mode 100644 index 0000000000..e181064bba --- /dev/null +++ b/src/main/java/budgetbuddy/storage/DataStorage.java @@ -0,0 +1,392 @@ +package budgetbuddy.storage; + +import budgetbuddy.account.Account; +import budgetbuddy.account.AccountManager; +import budgetbuddy.categories.Category; +import budgetbuddy.exceptions.FileCorruptedException; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.transaction.TransactionList; +import budgetbuddy.transaction.type.Expense; +import budgetbuddy.transaction.type.Income; +import budgetbuddy.transaction.type.Transaction; +import budgetbuddy.ui.UserInterface; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.logging.Logger; +import java.util.logging.Level; + +/** + * This class provides methods for storing and retrieving data related to transactions and accounts. + * It includes methods for saving and loading transactions and accounts to/from files. + */ +public class DataStorage { + public static final String TRANSACTIONS_FILE_PATH = "./data/transactions.txt"; + public static final String ACCOUNTS_FILE_PATH = "./data/accounts.txt"; + public static final String FOLDER_PATH = "./data"; + public static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + + //@@author ShyamKrishna33 + + /** + * Writes the provided string to a file at the given file path. + * + * @param stringToWrite The string to write to the file. + * @param filePath The path of the file to write to. + * @throws IOException If an I/O error occurs while writing to the file. + */ + private static void writeToFile(String stringToWrite, String filePath) throws IOException { + FileWriter fw = new FileWriter(filePath, true); + fw.write(stringToWrite); + fw.close(); + } + + private static String getStringToWrite(Transaction t) { + LocalDate date = t.getDate(); + String stringDate = date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")); + return t.getDescription() + " ," + t.getCategory().getCategoryNum() + " ," + + t.getTransactionType() + " ," + stringDate + " ," + t.getAmount() + " ," + t.getAccountNumber() + + " ," + t.getAccountName() + "\n"; + } + + private static void createDataFolderIfNotExists() throws IOException { + Path dataFolderPath = Paths.get(FOLDER_PATH); + if (!Files.exists(dataFolderPath)) { + Files.createDirectories(dataFolderPath); + } + } + //@@author + + /** + * Saves the list of accounts to the file in the ACCOUNT_FILE_PATH. + * + * @param accounts The list of accounts to save. + */ + public void saveAccounts(ArrayList accounts) { + LOGGER.log(Level.INFO, "Saving accounts to file"); + try { + File f = new File(ACCOUNTS_FILE_PATH); + if (!f.exists()) { + LOGGER.log(Level.WARNING, "File does not exist. Creating new file."); + createDataFolderIfNotExists(); + if (!f.createNewFile()) { + LOGGER.log(Level.SEVERE, "Failed to create file"); + throw new IOException("Failed to create file"); + } + } + FileWriter fw = new FileWriter(ACCOUNTS_FILE_PATH, false); + for (Account account : accounts) { + String stringToWrite = account.getAccountNumber() + " ," + account.getName() + " ," + + account.getBalance() + "\n"; + writeToFile(stringToWrite, ACCOUNTS_FILE_PATH); + } + fw.close(); + } catch (IOException e) { + System.out.println("Error saving accounts."); + LOGGER.log(Level.SEVERE, "Error saving accounts"); + } + LOGGER.log(Level.INFO, "Accounts saved to file"); + } + + //@@author ShyamKrishna33 + + /** + * Saves the list of transactions to a file. + * + * @param transactionArrayList The list of transactions to save. + * @throws IOException If an I/O error occurs while saving the transactions. + */ + public void saveTransactions(ArrayList transactionArrayList) throws IOException { + LOGGER.log(Level.INFO, "Saving transactions to file"); + File f = new File(TRANSACTIONS_FILE_PATH); + + assert f.exists() : "File does not exist"; + FileWriter fw = new FileWriter(TRANSACTIONS_FILE_PATH, false); + + for (Transaction transaction : transactionArrayList) { + if (transaction == null) { + break; + } + String stringToWrite = getStringToWrite(transaction); + writeToFile(stringToWrite, TRANSACTIONS_FILE_PATH); + } + LOGGER.log(Level.INFO, "Transactions saved to file"); + } + + /** + * Parses a string representing transaction data into a Transaction object. + * + * @param s The string representing the transaction data. + * @param existingAccountNumbers A list of existing account numbers. + * @return The parsed Transaction object. + * @throws FileCorruptedException If the file containing transaction data is corrupted. + * @throws InvalidCategoryException If the category specified in the transaction data is invalid. + */ + private Transaction parseDataToTransaction(String s, ArrayList existingAccountNumbers) + throws FileCorruptedException, InvalidCategoryException { + String[] transactionInfo = s.split(" ,"); + int categoryNum; + try { + categoryNum = Integer.parseInt(transactionInfo[1]); + } catch (NumberFormatException e) { + throw new FileCorruptedException("Invalid type for category number"); + } + + if (categoryNum < 1 || categoryNum > 9) { + throw new FileCorruptedException("Invalid category number"); + } + + if (transactionInfo.length != 7) { + throw new FileCorruptedException("Invalid transaction information format"); + } + + if (!transactionInfo[2].equals("Income") && !transactionInfo[2].equals("Expense")) { + throw new FileCorruptedException("Invalid transaction type"); + } + + double amount; + + try { + amount = Double.parseDouble(transactionInfo[4]); + } catch (NumberFormatException e) { + throw new FileCorruptedException("Invalid type for transaction amount"); + } + + if (!existingAccountNumbers.contains(Integer.parseInt(transactionInfo[5]))) { + throw new FileCorruptedException("Invalid account number"); + } + + switch (transactionInfo[2]) { + case "Income": + Income incomeObj = new Income(Integer.parseInt(transactionInfo[5]), transactionInfo[6], transactionInfo[0], + amount, transactionInfo[3]); + incomeObj.setCategory(Category.fromNumber(categoryNum)); + return incomeObj; + case "Expense": + Expense expenseObj = new Expense(Integer.parseInt(transactionInfo[5]), transactionInfo[6], + transactionInfo[0], -amount, transactionInfo[3]); + expenseObj.setCategory(Category.fromNumber(categoryNum)); + return expenseObj; + default: + return null; + } + } + //@@author + + /** + * Reads account data from a file and returns a list of Account objects. + * + * @param existingAccountNumbers A list of existing account numbers. + * @return The list of Account objects read from the file. + * @throws IOException If an I/O error occurs while reading the file. + * @throws FileCorruptedException If the file containing account data is corrupted. + */ + public ArrayList readAccountFile(ArrayList existingAccountNumbers) + throws IOException, FileCorruptedException { + LOGGER.log(Level.INFO, "Reading accounts from file"); + File f = new File(ACCOUNTS_FILE_PATH); + Scanner s = new Scanner(f); + + ArrayList accounts = new ArrayList<>(); + while (s.hasNext()) { + String line = s.nextLine(); + if (line.trim().isEmpty()) { + continue; + } + accounts.add(processAccountLine(line, existingAccountNumbers)); + } + LOGGER.log(Level.INFO, "Accounts read from file"); + return accounts; + } + + /** + * Reads account data from a file and returns a list of Account objects. + * + * @param existingAccountNumbers A list of existing account numbers. + * @return The list of Account objects read from the file. + * @throws FileCorruptedException If the file containing account data is corrupted. + */ + private Account processAccountLine(String line, ArrayList existingAccountNumbers) + throws FileCorruptedException { + LOGGER.log(Level.INFO, "Processing account line"); + String[] accountInfo = line.split(" ,"); + validateAccountInfo(accountInfo, existingAccountNumbers); + LOGGER.log(Level.INFO, "Account line processed"); + + int accountNumber = Integer.parseInt(accountInfo[0]); + double balance = Double.parseDouble(accountInfo[2]); + String accountName = accountInfo[1].trim(); + + existingAccountNumbers.add(accountNumber); + LOGGER.log(Level.INFO, "Account added to existing account numbers list"); + LOGGER.log(Level.INFO, "Account created"); + return new Account(accountNumber, accountName, balance); + } + + /** + * Validates a line of account data. + * + * @param accountInfo The line of account data to validate. + * @param existingAccountNumbers A list of existing account numbers. + * @throws FileCorruptedException If the line of account data is invalid. + */ + private void validateAccountInfo(String[] accountInfo, ArrayList existingAccountNumbers) + throws FileCorruptedException { + if (accountInfo.length != 3) { + LOGGER.log(Level.SEVERE, "Invalid account information format"); + throw new FileCorruptedException("Invalid account information format"); + } + + try { + int accountNumber = Integer.parseInt(accountInfo[0]); + if (accountNumber < 1000 || accountNumber > 9999) { + LOGGER.log(Level.SEVERE, "Invalid account number"); + throw new FileCorruptedException("Invalid account number"); + } + if (existingAccountNumbers.contains(accountNumber)) { + LOGGER.log(Level.SEVERE, "Duplicate account number"); + throw new FileCorruptedException("Duplicate account number"); + } + } catch (NumberFormatException e) { + LOGGER.log(Level.SEVERE, "Invalid type for account number"); + throw new FileCorruptedException("Invalid type for account number"); + } + + try { + double balance = Double.parseDouble(accountInfo[2]); + } catch (NumberFormatException e) { + LOGGER.log(Level.SEVERE, "Invalid type for account balance"); + throw new FileCorruptedException("Invalid type for account balance"); + } + + String accountName = accountInfo[1].trim(); + if (accountName.isEmpty()) { + LOGGER.log(Level.SEVERE, "Invalid account name"); + throw new FileCorruptedException("Invalid account name"); + } + } + + //@@author ShyamKrishna33 + /** + * Reads transaction data from the transactions file and returns a list of Transaction objects. + * + * @param existingAccountNumbers A list of existing account numbers. + * @return The list of Transaction objects read from the file. + * @throws IOException If an I/O error occurs while reading the file. + */ + public ArrayList readTransactionFile(ArrayList existingAccountNumbers) throws IOException { + LOGGER.log(Level.INFO, "Fetching transactions from storage"); + createDataFolderIfNotExists(); + File f = new File(TRANSACTIONS_FILE_PATH); + if (!f.exists()) { + LOGGER.log(Level.INFO, "File does not exists. Creating a new one."); + if (!f.createNewFile()) { + throw new IOException("Failed to create file"); + } + } + + assert f.exists() : "File does not exist"; + + Scanner s = new Scanner(f); + ArrayList transactionList = new ArrayList<>(); + try { + while (s.hasNext()) { + String line = s.nextLine(); + if (line.trim().isEmpty()) { + continue; + } + transactionList.add(parseDataToTransaction(line, existingAccountNumbers)); + } + } catch (FileCorruptedException | InvalidCategoryException e) { + LOGGER.log(Level.SEVERE, "File got corrupted"); + UserInterface.printFileCorruptedError(); + FileWriter fw = new FileWriter(TRANSACTIONS_FILE_PATH, false); + return new ArrayList<>(); + } + LOGGER.log(Level.INFO, "Transactions are fetched successfully"); + return transactionList; + } + //@@author + + /** + * Loads the accounts from the accounts file and returns an AccountManager object. + * + * @return The loaded AccountManager object. + */ + public AccountManager loadAccounts() { + LOGGER.log(Level.INFO, "Loading accounts from file"); + try { + File f = new File(ACCOUNTS_FILE_PATH); + if (!f.exists()) { + LOGGER.log(Level.WARNING, "File does not exist. Creating new file."); + createDataFolderIfNotExists(); + if (!f.createNewFile()) { + LOGGER.log(Level.SEVERE, "Failed to create file"); + throw new IOException("Failed to create file"); + } + return createNewAccountManager(); + } + ArrayList existingAccountNumbers = new ArrayList<>(); + ArrayList accounts = null; + try { + accounts = readAccountFile(existingAccountNumbers); + } catch (FileCorruptedException e) { + LOGGER.log(Level.SEVERE, "File corrupted"); + UserInterface.printFileCorruptedError(); + FileWriter fw = new FileWriter(ACCOUNTS_FILE_PATH, false); + LOGGER.log(Level.WARNING, "Creating new account manager"); + return createNewAccountManager(); + } + if (accounts.isEmpty()) { + LOGGER.log(Level.WARNING, "Creating new account manager"); + return createNewAccountManager(); + } + return new AccountManager(accounts, existingAccountNumbers); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error loading accounts"); + UserInterface.printFileCorruptedError(); + LOGGER.log(Level.WARNING, "Creating new account manager"); + return createNewAccountManager(); + } + } + + private AccountManager createNewAccountManager() { + String accountName = null; + try { + accountName = UserInterface.getInitialAccountName(); + } catch (InvalidArgumentSyntaxException e) { + UserInterface.printInvalidArgumentSyntax(e.getMessage()); + return createNewAccountManager(); + } + Double initialBalance = UserInterface.getInitialAccountBalance(); + AccountManager accountManager = new AccountManager(); + accountManager.addAccount(accountName, initialBalance); + return accountManager; + } + + //@@author ShyamKrishna33 + /** + * Loads the transactions from the transactions file and returns a TransactionList object. + * + * @param existingAccountNumbers A list of existing account numbers. + * @return The loaded TransactionList object. + */ + public TransactionList loadTransactions(ArrayList existingAccountNumbers) { + try { + ArrayList transactions = readTransactionFile(existingAccountNumbers); + return new TransactionList(transactions); + } catch (IOException e) { + return new TransactionList(); + } + } + //@@author +} diff --git a/src/main/java/budgetbuddy/transaction/TransactionList.java b/src/main/java/budgetbuddy/transaction/TransactionList.java new file mode 100644 index 0000000000..9da18b25df --- /dev/null +++ b/src/main/java/budgetbuddy/transaction/TransactionList.java @@ -0,0 +1,464 @@ +package budgetbuddy.transaction; + +import budgetbuddy.account.Account; + +import budgetbuddy.account.AccountManager; + + +import budgetbuddy.categories.Category; +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidAddTransactionSyntax; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.exceptions.InvalidEditTransactionData; +import budgetbuddy.exceptions.InvalidIndexException; +import budgetbuddy.exceptions.InvalidTransactionTypeException; +import budgetbuddy.insights.Insight; +import budgetbuddy.parser.Parser; +import budgetbuddy.storage.DataStorage; +import budgetbuddy.transaction.type.Transaction; +import budgetbuddy.ui.UserInterface; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Represents a list of transactions and provides methods for managing them. + */ +public class TransactionList { + + public static final int DELETE_BEGIN_INDEX = 7; + public static final int INDEX_OFFSET = 1; + public static final int LOWER_BOUND = 0; + public static final int EDIT_BEGIN_INDEX = 5; + public static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + + public static final String ACCOUNT = "acc"; + public static final String ALL = "all"; + public static final String ADD = "add"; + public static final String DELETE = "delete"; + public static final String EDIT = "edit"; + public static final String LIST = "list"; + public static final String SEARCH = "search"; + private static final int DAYS_IN_WEEK = 7; + private static final int DAYS_IN_MONTH = 30; + private static final int DAYS_OFFSET = 1; + + private final ArrayList transactions; + private final Parser parser; + private final DataStorage dataStorage = new DataStorage(); + + /** + * Constructs a new TransactionList object with an empty list of transactions and a parser. + */ + public TransactionList() { + this.transactions = new ArrayList<>(); + this.parser = new Parser(); + LOGGER.log(Level.INFO, "TransactionList created with empty transactions and parser"); + + } + + /** + * Constructs a new TransactionList object with the specified list of transactions and a parser. + * + * @param transactions The list of transactions. + */ + public TransactionList(ArrayList transactions) { + this.transactions = transactions; + this.parser = new Parser(); + LOGGER.log(Level.INFO, "TransactionList created with transactions and parser"); + + } + + public ArrayList getTransactions() { + return transactions; + } + + public void printTransactions() { + UserInterface.printAllTransactions(transactions); + } + + /** + * Removes a transaction from the list based on the user input. + * + * @param input The user input specifying the transaction to be removed. + * @param accountManager The account manager for retrieving account information. + * @throws EmptyArgumentException If the input string is empty or does not contain the required parameter. + * @throws NumberFormatException If the index parsed from the input string is not a valid integer. + * @throws InvalidIndexException If the index is out of bounds or invalid. + */ + public void removeTransaction(String input, AccountManager accountManager) throws EmptyArgumentException, + NumberFormatException, InvalidIndexException { + if (input.trim().length() < DELETE_BEGIN_INDEX) { + LOGGER.log(Level.WARNING, "Index id is not given for delete command"); + throw new EmptyArgumentException("delete index"); + } + String data = input.substring(DELETE_BEGIN_INDEX).trim(); + int id = Integer.parseInt(data) - INDEX_OFFSET; + int size = transactions.size(); + if (id >= LOWER_BOUND && id < size) { + String itemRemoved = transactions.get(id).toString(); + Account account = accountManager.getAccountByAccountNumber(transactions.get(id).getAccountNumber()); + assert itemRemoved != null : "String representation of item to remove is null"; + account.setBalance(account.getBalance() - transactions.get(id).getAmount()); + transactions.remove(id); + assert transactions.size() == size - 1 : "Transaction list size did not decrease after removal"; + UserInterface.printDeleteMessage(itemRemoved, account.getBalance()); + LOGGER.log(Level.INFO, "Transaction is removed successfully"); + + } else { + LOGGER.log(Level.WARNING, "Invalid index for delete command"); + throw new InvalidIndexException(String.valueOf(size)); + } + } + + public static boolean isNotInteger(String data) { + try { + Integer.parseInt(data); + return false; + } catch (NumberFormatException e) { + return true; + } + } + + public static boolean isNotDouble(String data) { + try { + Double.parseDouble(data); + return false; + } catch (NumberFormatException e) { + return true; + } + } + + void addTransaction(Transaction t) { + transactions.add(t); + } + + /** + * Processes a transaction based on the user input and adds it to the transaction list. + * + * @param input The user input specifying the transaction details. + * @param account The account associated with the transaction. + * @throws InvalidTransactionTypeException If the transaction type is invalid. + * @throws InvalidAddTransactionSyntax If the syntax for adding a transaction is invalid. + * @throws EmptyArgumentException If the input string is empty or missing required arguments. + * @throws InvalidCategoryException If the category specified in the transaction is invalid. + */ + public void processTransaction(String input, Account account) + throws InvalidTransactionTypeException, InvalidAddTransactionSyntax, EmptyArgumentException, + InvalidCategoryException { + // Check for syntax for add transaction + String[] arguments = {"/a/", "/t/", "/n/", "/$/", "/d/"}; + for (String argument : arguments) { + if (!input.contains(argument)) { + LOGGER.log(Level.WARNING, "Invalid add transaction syntax"); + throw new InvalidAddTransactionSyntax("Invalid add syntax."); + } + } + + Transaction t = parser.parseUserInputToTransaction(input, account); + assert t != null : "Parsed transaction is null"; + addTransaction(t); + assert transactions.get(transactions.size() - 1) != null : "Added transaction is null after adding to the list"; + String fetchData = String.valueOf(transactions.get(transactions.size() - 1)); + UserInterface.printAddMessage(fetchData, account.getBalance()); + LOGGER.log(Level.INFO, "Transaction added successfully"); + + } + + /** + * Saves the current list of transactions to a data storage. + * + * @throws IOException If an I/O error occurs while saving the transactions. + */ + public void saveTransactionList() throws IOException { + dataStorage.saveTransactions(transactions); + } + + /** + * Retrieves past transactions based on the specified duration. + * + * @param transactions The list of transactions to filter. + * @param duration The duration of past transactions to retrieve ("week" or "month"). + * @return An ArrayList containing past transactions based on the specified duration. + */ + //@@author isaaceng7 + public static ArrayList getPastTransactions(ArrayList transactions, String duration) { + LocalDate today = LocalDate.now(); + LocalDate startDate = null; + switch (duration) { + case "week": + startDate = today.minusDays(DAYS_IN_WEEK + DAYS_OFFSET); + break; + case "month": + startDate = today.minusDays(DAYS_IN_MONTH + DAYS_OFFSET); + break; + default: + break; + } + ArrayList pastTransactions = new ArrayList<>(); + for (Transaction transaction : transactions) { + if (transaction.getDate().isAfter(startDate)) { + pastTransactions.add(transaction); + } + } + LOGGER.log(Level.INFO, "Past transactions loaded successfully"); + return pastTransactions; + } + + /** + * Retrieves transactions within a custom date range. + * + * @param transactions The list of transactions to filter. + * @return An ArrayList containing transactions within the specified custom date range. + */ + public static ArrayList getCustomDateTransactions(ArrayList transactions) { + String start = UserInterface.getStartDate(); + String end = UserInterface.getEndDate(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + LocalDate startDate = LocalDate.parse(start, formatter).minusDays(DAYS_OFFSET); + LocalDate endDate = LocalDate.parse(end, formatter).plusDays(DAYS_OFFSET); + ArrayList customDateTransactions = new ArrayList<>(); + for (Transaction transaction : transactions) { + if (transaction.getDate().isAfter(startDate) && transaction.getDate().isBefore(endDate)) { + customDateTransactions.add(transaction); + } + } + LOGGER.log(Level.INFO, "Custom date transactions loaded successfully"); + + return customDateTransactions; + } + + /** + * Retrieves transactions associated with a specific account number. + * + * @param transactions The list of transactions to filter. + * @param accountNumber The account number for which transactions are to be retrieved. + * @return An ArrayList containing transactions associated with the specified account number. + */ + public static ArrayList getAccountTransactions(ArrayList transactions, + int accountNumber) { + ArrayList accountTransactions = new ArrayList<>(); + for (Transaction transaction : transactions) { + if (transaction.getAccountNumber() == accountNumber) { + accountTransactions.add(transaction); + } + } + LOGGER.log(Level.INFO, "Transaction based on account loaded successfully"); + return accountTransactions; + } + + /** + * Retrieves transactions associated with a specific category. + * + * @param transactions The list of transactions to filter. + * @param category The category for which transactions are to be retrieved. + * @return An ArrayList containing transactions associated with the specified category. + */ + public static ArrayList getCategoryTransactions(ArrayList transactions, + Category category) { + ArrayList categoryTransactions = new ArrayList<>(); + for (Transaction transaction : transactions) { + if (transaction.getCategory() == category) { + categoryTransactions.add(transaction); + } + } + LOGGER.log(Level.INFO, "Transactions based on a category loaded successfully"); + + return categoryTransactions; + } + + /** + * Processes the user-selected list option to list a specific set of transactions + * and perform the corresponding action. + * + * @param accounts The list of accounts. + * @param accountManager The account manager for retrieving account information. + * @throws InvalidIndexException If the selected option index is invalid. + * @throws InvalidCategoryException If the selected category is invalid. + */ + public void processList(ArrayList accounts, AccountManager accountManager) throws InvalidIndexException, + InvalidCategoryException { + UserInterface.printListOptions(); + String data = UserInterface.getListOption().trim(); + int option = Integer.parseInt(data); + switch (option) { + // 1 - ALL TRANSACTIONS + case 1: + printTransactions(); + break; + // 2 - PAST WEEK TRANSACTIONS + case 2: + ArrayList pastWeekTransactions = getPastTransactions(transactions, "week"); + UserInterface.printPastTransactions(pastWeekTransactions, "week"); + break; + // 3 - PAST MONTH TRANSACTIONS + case 3: + ArrayList pastMonthTransactions = getPastTransactions(transactions, "month"); + UserInterface.printPastTransactions(pastMonthTransactions, "month"); + break; + // 4 - CUSTOM DATE TRANSACTIONS + case 4: + ArrayList customDateTransactions = getCustomDateTransactions(transactions); + UserInterface.printCustomDateTransactions(customDateTransactions); + break; + // 5 - ACCOUNT TRANSACTIONS + case 5: + String accountData = UserInterface.getSelectedAccountNumber(accounts); + int accountNumber = Integer.parseInt(accountData); + Account account = accountManager.getAccountByAccountNumber(accountNumber); + String accountName = account.getName(); + ArrayList accountTransactions = getAccountTransactions(transactions, accountNumber); + UserInterface.printAccountTransactions(accountTransactions, accountName, accountNumber); + break; + // 6 - CATEGORY TRANSACTIONS + case 6: + UserInterface.listCategories(); + int input = UserInterface.getSelectedCategory(); + Category categorySelected = Category.fromNumber(input); + String categoryName = categorySelected.getCategoryName(); + ArrayList categoryTransactions = getCategoryTransactions(transactions, categorySelected); + UserInterface.printCategoryTransactions(categoryTransactions, categoryName); + break; + default: + LOGGER.log(Level.WARNING, "Invalid index for 'list' command"); + throw new InvalidIndexException("6"); + } + + } + + /** + * Processes the user input for editing a transaction and updates the transaction accordingly. + * + * @param input The user input specifying the transaction index to be edited. + * @param accountManager The account manager for retrieving account information. + * @throws EmptyArgumentException If the input string is empty or missing required arguments. + * @throws NumberFormatException If the index parsed from the input string is not a valid integer. + * @throws InvalidIndexException If the index is out of bounds or invalid. + * @throws InvalidEditTransactionData If the data for editing the transaction is invalid. + * @throws InvalidCategoryException If the specified category is invalid. + */ + //@@author Vavinan + public void processEditTransaction(String input, AccountManager accountManager) throws EmptyArgumentException, + NumberFormatException, InvalidIndexException, InvalidEditTransactionData, InvalidCategoryException, + InvalidArgumentSyntaxException { + if (input.trim().length() < EDIT_BEGIN_INDEX) { + LOGGER.log(Level.WARNING, "Index id is missing for edit command"); + throw new EmptyArgumentException("edit index "); + } + String data = input.substring(EDIT_BEGIN_INDEX).trim(); + + if (isNotInteger(data)) { + LOGGER.log(Level.WARNING, "Given index id for 'edit' command is not an integer"); + throw new NumberFormatException(data); + } + int index = Integer.parseInt(data) - INDEX_OFFSET; + if ((index >= LOWER_BOUND) && (index < transactions.size())) { + Transaction transaction = transactions.get(index); + Account account = accountManager.getAccountByAccountNumber(transaction.getAccountNumber()); + String newTransaction = UserInterface.getEditInformation(transaction.toString()); + Transaction t = parser.parseEditTransaction(newTransaction, account); + transactions.set(index, t); + UserInterface.printUpdatedTransaction(t); + LOGGER.log(Level.INFO, "Transaction is edited successfully"); + + } else { + LOGGER.log(Level.WARNING, "Given index id for 'edit' command is not valid"); + throw new InvalidIndexException(String.valueOf(transactions.size())); + } + } + + /** + * Provides help messages for user commands. + * + * @param input The user input specifying the command for which help is requested. + */ + public void helpWithUserCommands(String input) { + String helpCommand = parser.parseHelpCommand(input); + switch (helpCommand.toLowerCase()) { + case ALL: + UserInterface.printAllCommands(); + break; + case ADD: + UserInterface.printAddHelp(); + break; + case DELETE: + UserInterface.printDeleteHelp(); + break; + case EDIT: + UserInterface.printEditHelp(); + break; + case LIST: + UserInterface.printListHelp(); + break; + case ACCOUNT: + UserInterface.printAccountHelp(); + break; + + case SEARCH: + UserInterface.printSearchHelp(); + break; + default: + UserInterface.printUseAvailableHelp(); + break; + } + } + //@@author + + /** + * Displays insights based on transaction categories. + */ + //@@author ShyamKrishna33 + public void displayInsights() { + Insight.displayCategoryInsight(transactions); + } + + public ArrayList removeTransactionsByAccountNumber(int accountNumber) { + ArrayList transactionsToRemove = new ArrayList<>(); + for (Transaction transaction : transactions) { + if (transaction.getAccountNumber() == accountNumber) { + transactionsToRemove.add(transaction); + } + } + transactions.removeAll(transactionsToRemove); + LOGGER.log(Level.INFO, "Transactions were removed successfully from the specified account number"); + return transactionsToRemove; + } + + /** + * Searches transactions based on a keyword and prints search results. + * + * @param input The user input specifying the keyword to search for transactions. + */ + public void searchTransactions(String input) { + try { + String keyword = input.split(" ")[1]; + ArrayList searchResults = new ArrayList<>(); + ArrayList indices = new ArrayList<>(); + int index = 0; + for (Transaction transaction : transactions) { + if (transaction.getDescription().toLowerCase().contains(keyword.toLowerCase()) || + String.valueOf(transaction.getAmount()).contains(keyword) || + transaction.getCategory().getCategoryName().toLowerCase() + .contains(keyword.toLowerCase()) || + transaction.getDate().toString().contains(keyword)) { + searchResults.add(transaction); + indices.add(index); + } + index++; + } + LOGGER.log(Level.INFO, "Transactions are filtered out for 'search' command"); + UserInterface.printSearchResults(searchResults, indices); + } catch (ArrayIndexOutOfBoundsException e) { + LOGGER.log(Level.WARNING, "Keyword is not provided for search command"); + UserInterface.printInvalidInput("Please enter a keyword to search for transactions."); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Search command failed"); + UserInterface.printExceptionErrorMessage(e.getMessage()); + } + } +} diff --git a/src/main/java/budgetbuddy/transaction/type/Expense.java b/src/main/java/budgetbuddy/transaction/type/Expense.java new file mode 100644 index 0000000000..400a09553c --- /dev/null +++ b/src/main/java/budgetbuddy/transaction/type/Expense.java @@ -0,0 +1,59 @@ +package budgetbuddy.transaction.type; + +import budgetbuddy.account.Account; + +/** + * Represents an expense transaction in the budget buddy system. + * An expense transaction decreases the balance of an account. + */ +public class Expense extends Transaction { + private static final String TRANSACTION_TYPE = "Expense"; + + /** + * Creates an expense transaction with the given account number, account name, description, amount, date, + * and account. + * The amount is automatically negated to represent an expense. + * The balance of the account is decreased by the amount of the expense. + * + * @param accountNumber the account number + * @param accountName the name of the account + * @param description the description of the expense + * @param amount the amount of the expense + * @param date the date of the expense + * @param account the account the expense is made from + */ + public Expense(int accountNumber, String accountName, String description, double amount, String date, + Account account) { + super(accountNumber, accountName, description, -amount, date); + assert this.getAmount() < 0 : "Expense amount must be positive"; + assert description != null && !description.isEmpty() : "Description cannot be null or empty"; + assert date != null : "Date cannot be null"; + assert account != null : "Account cannot be null"; + + account.setBalance(account.getBalance() + this.getAmount()); + } + + /** + * Creates an expense transaction with the given account number, account name, description, amount, and date. + * The amount is automatically negated to represent an expense. + * + * @param accountNumber the account number + * @param accountName the name of the account + * @param description the description of the expense + * @param amount the amount of the expense + * @param date the date of the expense + */ + public Expense(int accountNumber, String accountName, String description, double amount, String date) { + super(accountNumber, accountName, description, -amount, date); + } + + /** + * Returns the type of the transaction. + * + * @return the type of the transaction + */ + @Override + public String getTransactionType() { + return TRANSACTION_TYPE; + } +} diff --git a/src/main/java/budgetbuddy/transaction/type/Income.java b/src/main/java/budgetbuddy/transaction/type/Income.java new file mode 100644 index 0000000000..9430bfd825 --- /dev/null +++ b/src/main/java/budgetbuddy/transaction/type/Income.java @@ -0,0 +1,59 @@ +package budgetbuddy.transaction.type; + +import budgetbuddy.account.Account; + +/** + * Represents an income transaction in the budget buddy system. + * An income transaction increases the balance of an account. + */ +public class Income extends Transaction { + private static final String TRANSACTION_TYPE = "Income"; + + //@@author vibes-863 + + /** + * Creates an income transaction with the given account number, account name, description, amount, date, + * and account. + * The balance of the account is increased by the amount of the income. + * + * @param accountNumber the account number + * @param accountName the name of the account + * @param description the description of the income + * @param amount the amount of the income + * @param date the date of the income + * @param account the account the income is made to + */ + public Income(int accountNumber, String accountName, String description, double amount, String date, + Account account) { + super(accountNumber, accountName, description, amount, date); + assert this.getAmount() > 0 : "Income amount must be positive"; + assert description != null && !description.isEmpty() : "Description cannot be null or empty"; + assert date != null : "Date cannot be null"; + assert account != null : "Account cannot be null"; + + account.setBalance(account.getBalance() + this.getAmount()); + } + + /** + * Creates an income transaction with the given account number, account name, description, amount, and date. + * + * @param accountNumber the account number + * @param accountName the name of the account + * @param description the description of the income + * @param amount the amount of the income + * @param date the date of the income + */ + public Income(int accountNumber, String accountName, String description, double amount, String date) { + super(accountNumber, accountName, description, amount, date); + } + + /** + * Returns the type of the transaction. + * + * @return the type of the transaction + */ + @Override + public String getTransactionType() { + return TRANSACTION_TYPE; + } +} diff --git a/src/main/java/budgetbuddy/transaction/type/Transaction.java b/src/main/java/budgetbuddy/transaction/type/Transaction.java new file mode 100644 index 0000000000..8b80ebf0bb --- /dev/null +++ b/src/main/java/budgetbuddy/transaction/type/Transaction.java @@ -0,0 +1,101 @@ +package budgetbuddy.transaction.type; + +import budgetbuddy.categories.Category; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Represents a transaction involving a financial account. + * This is an abstract class and must be subclassed to provide specific transaction types. + */ +public abstract class Transaction { + // The account number associated with the transaction + private final int accountNumber; + + // The name of the account associated with the transaction + private final String accountName; + + // A brief description of the transaction + private final String description; + + // The amount of money involved in the transaction + private final double amount; + + // The category of the transaction + private Category category; + + // The date of the transaction + private final LocalDate date; + + /** + * Constructs a new Transaction object with the specified parameters. + * + * @param accountNumber The account number associated with the transaction. + * @param accountName The name of the account associated with the transaction. + * @param description A brief description of the transaction. + * @param amount The amount of money involved in the transaction. + * @param date The date of the transaction in the format "dd-mm-yyyy". + */ + public Transaction(int accountNumber, String accountName, String description, double amount, String date) { + this.accountNumber = accountNumber; + this.accountName = accountName; + this.description = description; + this.amount = amount; + this.date = parseDate(date); + } + + public int getAccountNumber() { + return accountNumber; + } + + public String getAccountName() { + return accountName; + } + + public String getDescription() { + return description; + } + + public double getAmount() { + return amount; + } + + public LocalDate getDate() { + return date; + } + + /** + * Parses the date string into a LocalDate object. + * + * @param by The date string in the format "dd-MM-yyyy". + * @return The LocalDate object representing the date. + */ + private LocalDate parseDate(String by) { + return LocalDate.parse(by, DateTimeFormatter.ofPattern("dd-MM-yyyy")); + } + + public Category getCategory() { + return category; + } + + public abstract String getTransactionType(); + + /** + * Returns a string representation of the transaction. + */ + @Override + public String toString() { + return (" Account Number: " + getAccountNumber() + " | " + + " Account Name: " + getAccountName() + " | " + + " Transaction Type: " + getTransactionType() + " | " + + " Description: " + getDescription() + " | " + + " Date: " + getDate() + " | " + + " Amount: " + getAmount() + " | " + + " Category: " + getCategory().getCategoryName()); + } + + public void setCategory(Category category) { + this.category = category; + } +} diff --git a/src/main/java/budgetbuddy/ui/UserInterface.java b/src/main/java/budgetbuddy/ui/UserInterface.java new file mode 100644 index 0000000000..9158dd135e --- /dev/null +++ b/src/main/java/budgetbuddy/ui/UserInterface.java @@ -0,0 +1,1124 @@ +package budgetbuddy.ui; + +import budgetbuddy.account.Account; +import budgetbuddy.categories.Category; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.transaction.type.Transaction; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Manages Overall interaction between users and the program. + * It handles all the input and output related tasks. + */ +public class UserInterface { + + public static final int START_INDEX = 0; + + public static final String HELP_BORDER = "```````````````````````````````````````````````````"; + + private static final String LINE = "----------------------------------------------------"; + private static final String TABLE_BORDER = "____________________________________________________"; + private static final String ACCOUNT_TABLE_BORDER = "____________________________________________________"; + + private static final String TAB_SPACE = " "; + public static Scanner in = new Scanner(System.in); + + /** + * The function `listCategories` prints out the available categories along with + * their corresponding + * category names and numbers. + */ + public static void listCategories() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Here are the available categories:"); + for (Category category : Category.values()) { + System.out.println(TAB_SPACE + TAB_SPACE + category.getCategoryName() + ": " + category.getCategoryNum()); + } + System.out.println(LINE); + } + + /** + * The function prompts the user to enter a number between 1 and 9 to categorize + * a transaction and + * returns the input as an integer. + * + * @return The method `getCategoryNum()` returns an integer value representing + * the category number + * input by the user. + */ + public static int getCategoryNum() { + System.out.println("In which category do you want to list this transaction? [Enter number between 1 and 9]"); + String input = in.nextLine(); + return Integer.parseInt(input); + } + + /** + * The function `printDeleteMessage` takes a transaction string and balance as + * input, splits the + * transaction string, and prints a message confirming the deletion of the + * transaction along with + * the updated account balance. + * + * @param transaction The `transaction` parameter is a string that contains + * information about a + * transaction, with different parts separated by the "|" + * character. + * @param balance The `balance` parameter represents the updated account + */ + public static void printDeleteMessage(String transaction, double balance) { + String[] parts = transaction.split("\\|"); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Got it. I have removed the following transaction from the history \n"); + for (String part : parts) { + System.out.println(TAB_SPACE + part.trim()); + } + System.out.println("\n" + TAB_SPACE + "Your updated account balance is $" + balance); + System.out.println(LINE); + } + + //@@author Vavinan + /** + * The function `printAddMessage` takes a transaction string and balance amount, + * formats and prints + * the transaction details along with the updated balance. + * + * @param transaction The `transaction` parameter is a string that contains + * information about a transaction. + * @param balance The `balance` parameter in the `printAddMessage` method + * represents the updated + * account balance after a transaction has been added. + */ + public static void printAddMessage(String transaction, double balance) { + String[] parts = transaction.split("\\|"); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Got it. I have added the following transaction \n"); + for (String part : parts) { + System.out.println(TAB_SPACE + part.trim()); + } + System.out.println("\n" + TAB_SPACE + "Your updated account balance is $" + balance); + System.out.println(LINE); + } + + /** + * The function `printInvalidIndex` prints a message indicating an invalid index + * along with the + * valid index range. + * + * @param message The `message` parameter is a string that contains the error + * message to be + * displayed when an invalid index is provided. + * @param id The `id` parameter in the `printInvalidIndex` method + * represents the upper limit of the + * index range that is valid for the message being displayed. + */ + public static void printInvalidIndex(String message, int id) { + System.out.println(LINE); + System.out.println(TAB_SPACE + message); + System.out.println(TAB_SPACE + "Please use index within the range of: 1 to " + id); + System.out.println(LINE); + } + + /** + * The function `printExceptionErrorMessage` prints an error message with a + * given message and + * provides guidance on checking command syntax. + * + * @param message The `message` parameter is a string that represents the error + * message associated + * with the exception that occurred. + */ + public static void printExceptionErrorMessage(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + "An error occurred with the message: " + message); + System.out.println(TAB_SPACE + "Please check your command Syntax. \n" + TAB_SPACE + + " If you need assistance use `help` command to know further about each command syntax."); + System.out.println(LINE); + } + + /** + * The function `printInvalidInput` prints an error message surrounded by a line + * to indicate + * invalid input. + * + * @param message The `message` parameter is a String that contains the error + * message to be displayed. + */ + public static void printInvalidInput(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Error occurred with message: " + message); + System.out.println(LINE); + } + + /** + * The function `printInvalidAddSyntax` prints an error message along with a + * reminder to ensure + * correct argument entry. + * + * @param message The `message` parameter is a string that represents the error + * message to be displayed + * when the syntax for adding an item is invalid. + */ + //@@author isaaceng7 + public static void printInvalidAddSyntax(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + message); + System.out.println(TAB_SPACE + "Please ensure that you have entered all the arguments correctly."); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printTransactionTypeError` prints an error message for an + * invalid transaction type + * in Java. + * + * @param message The `message` parameter is a string that represents the + * specific error message related + * to an invalid transaction type. + */ + //@@author isaaceng7 + public static void printTransactionTypeError(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Invalid transaction type: " + message); + System.out.println(TAB_SPACE + "Please enter Expense or Income only."); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printNumberFormatError` prints an error message related to + * number format issues in + * Java. + * + * @param message The `message` parameter is a string that represents the error + * message + * related to a number format issue. + */ + //@@author isaaceng7 + public static void printNumberFormatError(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Error occurred with the input: " + message); + System.out.println(TAB_SPACE + "Please enter a valid value."); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printEmptyArgumentError` prints an error message reminding the + * user to include + * proper argument in a command. + * + * @param message The `message` parameter is used to specify the type of + * argument that is + * missing in the command. + */ + //@@author isaaceng7 + public static void printEmptyArgumentError(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Please include the " + message + "in the command."); + System.out.println(LINE); + } + + //@@author + + /** + * The `printAllTransactions` function prints a formatted list of transaction + * details from an + * ArrayList of Transaction objects. + * + * @param transactions The `printAllTransactions` method takes an `ArrayList` of + * `Transaction` + * objects as a parameter. It then iterates over each + * `Transaction` object in the list and prints + * out the details of each transaction in a formatted + * table-like structure. + */ + public static void printAllTransactions(ArrayList transactions) { + int index = transactions.size(); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Your Transaction history:"); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5s %-10s %-20s %-20s %-30s %-15s %-15s %-15s%n", "ID", "Type", + "Account Number", "Account Name", "Transaction", "Date", "Amount", "Category"); + for (int i = START_INDEX; i < index; i++) { + Transaction transaction = transactions.get(i); + int accountNumber = transaction.getAccountNumber(); + String accountName = transaction.getAccountName(); + String type = transaction.getTransactionType(); + String description = transaction.getDescription(); + LocalDate date = transaction.getDate(); + double amount = transaction.getAmount(); + String category = transaction.getCategory().getCategoryName(); + + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5d %-10s %-20d %-20.45s %-30.45s %-15s %-15.2f %-15s%n", + i + 1, type, accountNumber, accountName, description, date, amount, category); + } + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.println(LINE); + } + + /** + * The function `printGoodBye` prints a farewell message with a reminder to keep + * track of future + * transactions. + */ + public static void printGoodBye() { + System.out.println(LINE); + System.out.println(TAB_SPACE + + "Bye... Don't forget to keep track of your future transactions"); + System.out.println(LINE); + } + + /** + * The function `printNoCommandExists` prints a message indicating that no such + * command exists. + */ + public static void printNoCommandExists() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "No such command exists."); + System.out.println(LINE); + } + + //@@author Vavinan + /** + * The function `getEditInformation` prompts the user to edit transaction + * details and returns the + * updated information as a formatted string. + * + * @param string The `getEditInformation` method takes a `String` parameter + * named `string`, which + * represents the transaction information that needs to be edited. + * The method then prompts the user + * to enter new values for the transaction type, description, + * date, amount, and category. + * @return The `getEditInformation` method is returning a formatted string that + * contains the edited + * transaction information. + */ + public static String getEditInformation(String string) throws InvalidArgumentSyntaxException { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Please edit the following transaction"); + System.out.println(string); + System.out.println(LINE); + System.out.print(TAB_SPACE + "Enter transaction type: "); + String type = in.nextLine(); + System.out.print(TAB_SPACE + "Enter description: "); + String description = in.nextLine(); + if (description.contains(",")) { + throw new InvalidArgumentSyntaxException("Input cannot contain ',' comma."); + } + System.out.print(TAB_SPACE + "Enter transaction date: "); + String date = in.nextLine(); + System.out.print(TAB_SPACE + "Enter transaction amount: "); + String amount = in.nextLine(); + System.out.println(" "); + for (Category category : Category.values()) { + System.out.println(TAB_SPACE + TAB_SPACE + category.getCategoryName() + ": " + category.getCategoryNum()); + } + System.out.println("In which category do you want to list this transaction? [Enter number between 1 and 9]"); + System.out.print(TAB_SPACE + "Enter Category: "); + String category = in.next(); + in.nextLine(); + return type + " | " + description + " | " + date + " | " + amount + " | " + category; + + } + //@@author + + /** + * The function `printUpdatedTransaction` prints a success message along with + * the updated + * transaction details. + * + * @param t The parameter `t` is an object of the `Transaction` class. This + * method is designed to + * print a message indicating that the transaction was updated + * successfully, followed by + * the details of the updated transaction object `t`. + */ + public static void printUpdatedTransaction(Transaction t) { + System.out.println("\n" + TAB_SPACE + "Updated Successfully"); + System.out.println(t.toString()); + System.out.println(LINE); + } + + /** + * The function `printAllCommands` prints a list of available commands with + * their syntax and + * further help options. + */ + public static void printAllCommands() { + System.out.println(HELP_BORDER); + System.out.printf("%-20s %-75s %-20s%n", "Command", "Syntax", "Further help"); + System.out.printf("%-20s %-75s %-20s%n", "Add", "add /t/[TYPE] /n/[DESCRIPTION] /d/[DD-MM-YYYY] " + + "/$/[AMOUNT] /c/[CATEGORY]", "help add"); + System.out.printf("%-20s %-75s %-20s%n", "Edit", "edit [INDEX]", "help edit"); + System.out.printf("%-20s %-75s %-20s%n", "Delete", "delete [INDEX]", "help delete"); + System.out.printf("%-20s %-75s %-20s%n", "List", "list", "help list"); + System.out.println(HELP_BORDER); + + } + + /** + * The function `printAddHelp` prints out helpful information and syntax + * examples for adding + * entries with different categories. + */ + public static void printAddHelp() { + System.out.println(HELP_BORDER); + System.out.println("Method 1:"); + System.out.println(TAB_SPACE + "SYNTAX : add /t/[TYPE] /n/[DESCRIPTION] /d/[DD-MM-YYYY]" + + "/$/[AMOUNT] \n"); + System.out.println("followed by choosing category from the given list:"); + + for (Category category : Category.values()) { + System.out.println(TAB_SPACE + TAB_SPACE + category.getCategoryName() + ": " + category.getCategoryNum()); + } + + System.out.println("\n Method 2"); + System.out.println(TAB_SPACE + " SYNTAX : add /t/[TYPE] /n/[DESCRIPTION] /d/[DD-MM-YYYY] " + + "/$/[AMOUNT] /c/[CATEGORY] \n"); + System.out.println("Provide the category number along with the add command"); + System.out.println("\n "); + System.out.println(HELP_BORDER); + } + + /** + * The function `printDeleteHelp` prints out syntax and guidelines for deleting + * an item from a + * transaction list. + */ + public static void printDeleteHelp() { + System.out.println(HELP_BORDER); + System.out.println(TAB_SPACE + "SYNTAX : delete [INDEX] \n"); + System.out.println(TAB_SPACE + "Make sure the index is above 0 and below or equal to the size of " + + "the transaction list"); + System.out.println(HELP_BORDER); + } + + /** + * The function `printEditHelp` provides guidance on how to use the "edit" + * command in a Java + * program for managing transactions. + */ + public static void printEditHelp() { + System.out.println(HELP_BORDER); + System.out.println(TAB_SPACE + "SYNTAX : edit [INDEX] \n"); + System.out.println("Make sure the index is above 0 and below or equal to the size of the " + + "transaction list"); + System.out.println("Then you will be asked to input the data for each parameters like"); + System.out.println(" Enter transaction type: [EXPENSE / INCOME] \n" + + " Enter description: [NEW DESCRIPTION] \n" + + " Enter transaction date: [NEW DATE] \n" + + " Enter transaction amount: [NEW AMOUNT] \n" + " \n"); + System.out.println("Then you will be given categories to choose from (like as add command). \n" + + " In which category do you want to list this transaction? [Enter number between 1 and " + + "9]\n" + + " Enter Category: [NEW CATEGORY] "); + System.out.println(HELP_BORDER); + } + + /** + * The function `printListHelp` prints out a help message with syntax and + * available options for + * viewing transactions. + */ + public static void printListHelp() { + System.out.println(HELP_BORDER); + System.out.println(TAB_SPACE + "SYNTAX : list \n"); + System.out.println(TAB_SPACE + "This will give some available options to choose from:"); + System.out.println("What would you like to view?\n" + + " 1. All Transactions\n" + + " 2. Past Week Transactions\n" + + " 3. Past Month Transactions\n" + + " 4. Custom Date Transactions\n"); + System.out.println("From this you can choose 1-4 :"); + System.out.println("To print Custom date transaction: \n" + + " 4\n" + "Start Date: [dd-MM-yyyy]\n" + "End Date: [dd-MM-yyyy] "); + System.out.println(HELP_BORDER); + } + + /** + * The function `printUseAvailableHelp` prints out a list of available commands + * for getting help + * related to transactions and accounts. + */ + public static void printUseAvailableHelp() { + System.out.println(HELP_BORDER); + System.out.println(TAB_SPACE + "Please use the following commands for help"); + System.out.println(TAB_SPACE + "To get idea about all commands use: help all"); + System.out.println(TAB_SPACE + "Add transaction: help add"); + System.out.println(TAB_SPACE + "Delete transaction: help delete"); + System.out.println(TAB_SPACE + "Search : help search"); + System.out.println(TAB_SPACE + "Edit transaction: help edit"); + System.out.println(TAB_SPACE + "List transaction: help list"); + System.out.println(TAB_SPACE + "Help related to accounts: help acc"); + System.out.println(HELP_BORDER); + } + + //@@author isaaceng7 + /** + * The function `printListOptions` displays a menu of transaction viewing + * options. + */ + public static void printListOptions() { + System.out.println(LINE); + System.out.println("What would you like to view?"); + System.out.println(TAB_SPACE + "1. All Transactions"); + System.out.println(TAB_SPACE + "2. Past Week Transactions"); + System.out.println(TAB_SPACE + "3. Past Month Transactions"); + System.out.println(TAB_SPACE + "4. Custom Date Transactions"); + System.out.println(TAB_SPACE + "5. Account Transactions"); + System.out.println(TAB_SPACE + "6. Category Transactions"); + System.out.println(LINE); + } + //@@author + + /** + * The function `getListOption` reads a user input as a String and returns it. + * + * @return This method returns a String value that is read from the user input + * using the `Scanner` object `in`. + */ + //@@author isaaceng7 + public static String getListOption() { + String data = in.next(); + in.nextLine(); + return data; + } + + //@@author + + /** + * The function `getStartDate()` prompts the user for a start date input and + * returns the entered + * data as a String. + * + * @return The method `getStartDate()` returns a String value, which is the user + * input for the + * start date. + */ + //@@author isaaceng7 + public static String getStartDate() { + System.out.print("Start Date: "); + String data = in.next(); + in.nextLine(); + return data; + } + + //@@author + + /** + * The function `getEndDate()` prompts the user for an end date input and + * returns the entered data + * as a String. + * + * @return The method `getEndDate()` is returning a String value that represents + * the end date + * entered by the user. + */ + //@@author isaaceng7 + public static String getEndDate() { + System.out.print("End Date: "); + String data = in.next(); + in.nextLine(); + return data; + } + + //@@author + + /** + * The function `printAccountList` prints a list of account numbers and names in + * a formatted table. + * + * @param accounts An ArrayList of Account objects. Each Account object + * represents an account with + * an account number and a name. + */ + //@@author isaaceng7 + public static void printAccountList(ArrayList accounts) { + int maxIndex = accounts.size(); + System.out.println(LINE); + System.out.println("Please select an account to view..."); + System.out.println(TAB_SPACE + "Your accounts:"); + System.out.println(TAB_SPACE + ACCOUNT_TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-20s %-30s", "Account Number", + "Account Name"); + + for (int i = START_INDEX; i < maxIndex; i++) { + Account account = accounts.get(i); + int accountNumber = account.getAccountNumber(); + String name = account.getName(); + + System.out.printf("\n" +TAB_SPACE + TAB_SPACE + "%-20d %-30.45s", accountNumber, name); + } + System.out.println("\n" + TAB_SPACE + ACCOUNT_TABLE_BORDER); + System.out.println(LINE); + } + //@@author + + /** + * The function `getSelectedAccountNumber` prompts the user to select an account + * number from a list + * of accounts and returns the selected account number as a String. + * + * @param accounts An ArrayList of Account objects. + * @return The method `getSelectedAccountNumber` returns a `String` value, which + * is the account + * number selected by the user from the list of accounts provided. + */ + //@@author isaaceng7 + public static String getSelectedAccountNumber(ArrayList accounts) { + printAccountList(accounts); + System.out.print("Account number: "); + String data = in.next(); + in.nextLine(); + return data; + } + + //@@author + + /** + * The function `getSelectedCategory` prompts the user to select a category + * number and returns the + * integer value entered by the user. + * + * @return The method `getSelectedCategory` is returning an integer value + * representing the selected + * category number entered by the user. + */ + //@@author isaaceng7 + public static int getSelectedCategory() { + System.out.print("Select a category number: "); + String data = in.nextLine(); + return Integer.parseInt(data); + } + + //@@author + + /** + * The function `printPastTransactions` prints a formatted list of past + * transactions based on a + * specified duration. + * + * @param transactions transactions is an ArrayList that contains Transaction + * objects. + * @param duration The `duration` parameter is a String that represents the + * time period for + * which the user want to display past transactions. + */ + //@@author isaaceng7 + public static void printPastTransactions(ArrayList transactions, String duration) { + int index = transactions.size(); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Displaying transactions from the past " + duration + ":"); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5s %-10s %-20s %-20s %-30s %-15s %-15s %-15s%n", "ID", "Type", + "Account Number", "Account Name", "Transaction", "Date", "Amount", "Category"); + for (int i = START_INDEX; i < index; i++) { + Transaction transaction = transactions.get(i); + int accountNumber = transaction.getAccountNumber(); + String accountName = transaction.getAccountName(); + String type = transaction.getTransactionType(); + String description = transaction.getDescription(); + LocalDate date = transaction.getDate(); + double amount = transaction.getAmount(); + String category = transaction.getCategory().getCategoryName(); + + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5d %-10s %-20d %-20.45s %-30.45s %-15s %-15.2f %-15s%n", + i + 1, type, accountNumber, accountName, description, date, amount, category); + } + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.println(LINE); + + } + + //@@author + + /** + * The `printCustomDateTransactions` function prints a formatted list of + * transactions within a + * specified date range. + * + * @param transactions The `printCustomDateTransactions` method takes an + * `ArrayList` of + * `Transaction` objects as input. It then iterates over the + * transactions in the list and prints + * out specific details of each transaction in a formatted + * table-like structure. + */ + //@@author isaaceng7 + public static void printCustomDateTransactions(ArrayList transactions) { + int index = transactions.size(); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Displaying transactions of specified date range:"); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5s %-10s %-20s %-20s %-30s %-15s %-15s %-15s%n", "ID", "Type", + "Account Number", "Account Name", "Transaction", "Date", "Amount", "Category"); + for (int i = START_INDEX; i < index; i++) { + Transaction transaction = transactions.get(i); + int accountNumber = transaction.getAccountNumber(); + String accountName = transaction.getAccountName(); + String type = transaction.getTransactionType(); + String description = transaction.getDescription(); + LocalDate date = transaction.getDate(); + double amount = transaction.getAmount(); + String category = transaction.getCategory().getCategoryName(); + + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5d %-10s %-20d %-20.45s %-30.45s %-15s %-15.2f %-15s%n", + i + 1, type, accountNumber, accountName, description, date, amount, category); + } + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printAccountTransactions` prints a formatted list of + * transactions for a specific + * account. + * + * @param transactions An ArrayList of Transaction objects containing the + * transaction details. + * @param accountName The `accountName` parameter in the + * `printAccountTransactions` method is a + * `String` that represents the name of the account for + * which the user want to display transactions. + * @param accountNumber The `accountNumber` parameter in the + * `printAccountTransactions` method is + * an integer value that represents the account number of + * the account for which the user want to display + * transactions. + */ + //@@author isaaceng7 + public static void printAccountTransactions(ArrayList transactions, String accountName, + int accountNumber) { + int index = transactions.size(); + System.out.println(LINE); + System.out + .println(TAB_SPACE + "Displaying transactions of account: " + accountName + "(" + accountNumber + ")"); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5s %-10s %-30s %-15s %-15s %-15s%n", "ID", "Type", + "Transaction", "Date", "Amount", "Category"); + for (int i = START_INDEX; i < index; i++) { + Transaction transaction = transactions.get(i); + String type = transaction.getTransactionType(); + String description = transaction.getDescription(); + LocalDate date = transaction.getDate(); + double amount = transaction.getAmount(); + String category = transaction.getCategory().getCategoryName(); + + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5d %-10s %-30.45s %-15s %-15.2f %-15s%n", + i + 1, type, description, date, amount, category); + } + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printCategoryTransactions` prints out a formatted list of + * transactions belonging + * to a specific category. + * + * @param transactions transactions is an ArrayList that contains Transaction + * objects. + * @param categoryName Category name is a string representing the name of a + * category for which the + * user want to display transactions. + */ + //@@author isaaceng7 + public static void printCategoryTransactions(ArrayList transactions, String categoryName) { + int index = transactions.size(); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Displaying transactions of category: " + categoryName); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5s %-10s %-20s %-20s %-30s %-15s %-15s%n", "ID", "Type", + "Account Number", "Account Name", "Transaction", "Date", "Amount"); + for (int i = START_INDEX; i < index; i++) { + Transaction transaction = transactions.get(i); + int accountNumber = transaction.getAccountNumber(); + String accountName = transaction.getAccountName(); + String type = transaction.getTransactionType(); + String description = transaction.getDescription(); + LocalDate date = transaction.getDate(); + double amount = transaction.getAmount(); + + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5d %-10s %-20d %-20.45s %-30.45s %-15s %-15.2f%n", + i + 1, type, accountNumber, accountName, description, date, amount); + } + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printAddAccountMessage` takes a string representing an account, + * splits it into + * parts, and prints each part with proper formatting. + * + * @param account The parameter `account` is expected to contain account + * information separated + * by the pipe character `|`. + */ + //@@author vibes-863 + public static void printAddAccountMessage(String account) { + String[] parts = account.split("\\|"); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Got it. I have added the following account \n"); + for (String part : parts) { + System.out.println(TAB_SPACE + part.trim()); + } + System.out.println(LINE); + } + + //@@author + + /** + * The function `printInvalidArgumentSyntax` prints an error message along with + * a reminder to + * ensure correct argument entry. + * + * @param message The `message` parameter is a string that contains the specific + * error message to + * be displayed when the argument syntax is invalid. + */ + //@@author vibes-863 + public static void printInvalidArgumentSyntax(String message) { + System.out.println(LINE); + System.out.println(TAB_SPACE + message); + System.out.println(TAB_SPACE + "Please ensure that you have entered all the arguments correctly."); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printListOfAccounts` prints a formatted list of account details + * including account + * number, name, and balance. + * + * @param accounts An ArrayList of Account objects. + */ + //@@author vibes-863 + public static void printListOfAccounts(ArrayList accounts) { + int maxIndex = accounts.size(); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Your accounts:"); + System.out.println(TAB_SPACE + ACCOUNT_TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-20s %-30s %-15s ", "Account Number", + "Account Name", "Balance"); + + for (int i = START_INDEX; i < maxIndex; i++) { + Account account = accounts.get(i); + int accountNumber = account.getAccountNumber(); + String name = account.getName(); + double balance = account.getBalance(); + + System.out.printf("\n" + TAB_SPACE + TAB_SPACE + "%-20d %-30.45s %-15.2f", accountNumber, name, balance); + } + System.out.println("\n" + TAB_SPACE + ACCOUNT_TABLE_BORDER); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printDeleteAccountMessage` prints a message confirming the + * deletion of an account + * and lists the transactions related to that account that have also been + * removed. + * + * @param account The `account` parameter is a string that contains + * account information. + * @param transactionsRemoved The `transactionsRemoved` parameter is an + * ArrayList of + * Transaction objects that represent the + * transactions related to the account + */ + //@@author vibes-863 + public static void printDeleteAccountMessage(String account, ArrayList transactionsRemoved) { + String[] parts = account.split("\\|"); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Got it. I have removed the following account \n"); + for (String part : parts) { + System.out.println(TAB_SPACE + part.trim()); + } + System.out.println("\n" + TAB_SPACE + "Transactions related to this account have also been removed:"); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-10s %-30s %-15s %-15s %-15s%n", "Type", "Transaction", "Date", + "Amount", "Category"); + for (Transaction transaction : transactionsRemoved) { + String type = transaction.getTransactionType(); + String description = transaction.getDescription(); + LocalDate date = transaction.getDate(); + double amount = transaction.getAmount(); + String category = transaction.getCategory().getCategoryName(); + + System.out.printf(TAB_SPACE + TAB_SPACE + "%-10s %-30.45s %-15s %-15.2f %-15s%n", type, description, date, + amount, category); + } + System.out.println(TAB_SPACE + TABLE_BORDER); + + System.out.println(LINE); + } + + //@@author + + /** + * The function `getNewAccountName` splits an account string into parts, + * displays them for editing, + * prompts the user to enter a new account name, and returns the trimmed input. + * + * @param account The `getNewAccountName` method takes a `String` parameter + * named `account`, which + * is expected to be in a specific format separated by the pipe + * character `|`. + * @return The method `getNewAccountName` returns a new account name entered by + * the user. + */ + //@@author vibes-863 + public static String getNewAccountName(String account) throws InvalidArgumentSyntaxException { + String[] parts = account.split("\\|"); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Please edit the following account \n"); + for (String part : parts) { + System.out.println(TAB_SPACE + part.trim()); + } + System.out.println(LINE); + System.out.print(TAB_SPACE + "Enter new account name: "); + String name = in.nextLine().trim(); + if (name.contains(",")) { + throw new InvalidArgumentSyntaxException("Input cannot contain ',' comma."); + } + return name; + } + + //@@author + + /** + * The function `printUpdatedAccount` splits a given account string into parts, + * prints a success + * message, and then prints each part of the account details. + * + * @param account The `printUpdatedAccount` method takes a `String` parameter + * named `account`, + * which represents the account details. + */ + //@@author vibes-863 + public static void printUpdatedAccount(String account) { + String[] parts = account.split("\\|"); + System.out.println(LINE); + System.out.println(TAB_SPACE + "Updated Successfully! Updated account details: \n"); + for (String part : parts) { + System.out.println(TAB_SPACE + part.trim()); + } + System.out.println(LINE); + } + + //@@author + + /** + * The function `printCannotDeleteLastAccountMessage` prints a message + * indicating that the last + * account cannot be deleted. + */ + //@@author vibes-863 + public static void printCannotDeleteLastAccountMessage() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "You cannot delete the last account."); + System.out.println(TAB_SPACE + "Please edit the current account or add a new account before deleting " + + "this one."); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printAccountHelp()` prints a help menu for account-related + * commands in a Java + * program. + */ + //@@author Vavinan + public static void printAccountHelp() { + System.out.println(HELP_BORDER); + System.out.printf("%-30s %-90s%n", "Command", "Syntax"); + System.out.printf("%-30s %-90s%n", "Add account", "add-acc /n/ [ACCOUNT_NAME] /$/ " + + "[INITIAL_BALANCE]"); + System.out.printf("%-30s %-90s%n", "Delete Account", "delete-acc [ACCOUNT_NUMBER]"); + System.out.printf("%-30s %-90s%n", "Delete Account", "edit-acc [ACCOUNT_NUMBER]"); + System.out.printf("%-30s %-90s%n", "List all Accounts", "list-acc"); + System.out.println(HELP_BORDER); + } + + /** + * The function `printFileCorruptedError` prints an error message indicating + * that the storage file + * is corrupted and informs that a new file will be created. + */ + //@@author ShyamKrishna33 + public static void printFileCorruptedError() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "The storage file is corrupted :("); + System.out.println(TAB_SPACE + "So, a new file will be created!"); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printSearchResults` prints search results of transactions with + * specific formatting + * and handles cases where no matching transactions are found. + * + * @param transactions The `transactions` parameter is an ArrayList of + * Transaction objects. Each + * Transaction object represents a specific transaction with + * details such as transaction type, + * account number, account name, description, date, amount, + * and category. + * @param indices The `indices` parameter in the `printSearchResults` + * method is an `ArrayList` of + * `Integer` values. These indices represent the positions + * of the matching transactions in the + * original list of transactions. The method uses these + * indices to display the search results with + * the corresponding transaction number. + */ + //@@author Vavinan + public static void printSearchResults(ArrayList transactions, ArrayList indices) { + if (transactions.isEmpty()) { + System.out.println("No matching Transactions found"); + } else { + System.out.println("Search results:"); + System.out.println(TAB_SPACE + TABLE_BORDER); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5s %-10s %-20s %-20s %-30s %-15s %-15s %-15s%n", "ID", "Type", + "Account Number", "Account Name", "Transaction", "Date", "Amount", "Category"); + int i = 0; + for (Transaction t : transactions) { + // System.out.println((indices.get(i) + 1) + ". " + transactions.get(i)); + System.out.printf(TAB_SPACE + TAB_SPACE + "%-5d %-10s %-20d %-20.45s %-30.45s %-15s %-15.2f %-15s%n", + indices.get(i) + 1, t.getTransactionType(), t.getAccountNumber(), t.getAccountName(), + t.getDescription(), t.getDate(), t.getAmount(), t.getCategory().getCategoryName()); + i++; + } + System.out.println(TAB_SPACE + TABLE_BORDER); + } + + } + + /** + * The function `printInvalidCategoryError` prints an error message for an + * invalid category. + */ + //@@author ShyamKrishna33 + public static void printInvalidCategoryError() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Invalid Category"); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printInvalidAccountNameError` prints an error message stating + * that the Account + * Name cannot be blank. + */ + //@@author ShyamKrishna33 + private static void printInvalidAccountNameError() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Account Name cannot be blank"); + System.out.println(LINE); + } + + //@@author + + /** + * The function `printInvalidAccountBalanceError` prints an error message the + * invalid account balance. + */ + //@@author ShyamKrishna33 + private static void printInvalidAccountBalanceError() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Account Balance cannot be a valid number"); + System.out.println(LINE); + } + + //@@author + + /** + * The function `getInitialAccountName` prompts the user to input a name for + * their account and + * validates it to ensure it is not empty. + * + * @return The method `getInitialAccountName()` returns a `String` value, which + * is the account name + * entered by the user. + */ + //@@author ShyamKrishna33 + public static String getInitialAccountName() throws InvalidArgumentSyntaxException { + System.out.println("Let's first create an account for you! What do you want to call it?"); + String accountName = in.nextLine(); + if (accountName.contains(",")) { + throw new InvalidArgumentSyntaxException("Input cannot contain ',' comma."); + } + + if (accountName.trim().isEmpty()) { + printInvalidAccountNameError(); + accountName = getInitialAccountName(); + } + return accountName; + } + + //@@author + + /** + * The function `getInitialAccountBalance` prompts the user to input an initial + * account balance and + * handles any input errors. + * + * @return The method `getInitialAccountBalance()` returns a `Double` value, + * which represents the + * initial account balance entered by the user. + */ + //@@author ShyamKrishna33 + public static Double getInitialAccountBalance() { + System.out.println("Great! What's the initial balance?"); + double initialBalance = 0; + try { + initialBalance = Double.parseDouble(UserInterface.in.nextLine().trim()); + } catch (NumberFormatException e) { + printInvalidAccountBalanceError(); + getInitialAccountBalance(); + } + return initialBalance; + } + + public static void printSearchHelp(){ + System.out.println(HELP_BORDER); + System.out.println(TAB_SPACE + "SYNTAX : search [KEYWORD] \n"); + System.out.println(TAB_SPACE + "The keyword can be anything representing description, date, " + + "category or amount"); + System.out.println(HELP_BORDER); + } + + /** + * The function `printLoggerSetupError` prints an error message indicating that + * there was an issue + * setting up the logger. + */ + public static void printLoggerSetupError() { + System.out.println(LINE); + System.out.println(TAB_SPACE + "Error setting up logger"); + System.out.println(LINE); + } +} 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/budgetbuddy/account/AccountManagerTest.java b/src/test/java/budgetbuddy/account/AccountManagerTest.java new file mode 100644 index 0000000000..e99f6d993f --- /dev/null +++ b/src/test/java/budgetbuddy/account/AccountManagerTest.java @@ -0,0 +1,58 @@ +package budgetbuddy.account; + +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidArgumentSyntaxException; +import budgetbuddy.exceptions.InvalidIndexException; +import budgetbuddy.transaction.TransactionList; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AccountManagerTest { + + @Test + void addingAccountShouldIncreaseExistingAccountNumbersSize() { + AccountManager accountManager = new AccountManager(); + accountManager.addAccount("Test Account", 1000.00); + assertEquals(1, accountManager.getExistingAccountNumbers().size()); + } + + @Test + void addingAccountShouldGenerateUniqueAccountNumber() { + AccountManager accountManager = new AccountManager(); + accountManager.addAccount("Test Account", 1000.00); + accountManager.addAccount("Test Account 2", 2000.00); + assertNotEquals(accountManager.getAccount(0).getAccountNumber(), accountManager + .getAccount(1).getAccountNumber()); + } + + @Test + void removingAccountShouldDecreaseExistingAccountNumbersSize() + throws InvalidIndexException, InvalidArgumentSyntaxException, EmptyArgumentException { + AccountManager accountManager = new AccountManager(); + accountManager.addAccount("Test Account", 1000.00); + accountManager.addAccount("Test Account2", 1000.00); + accountManager.removeAccount("delete-acc " + String.valueOf(accountManager.getAccounts().get(1) + .getAccountNumber()), new TransactionList()); + assertEquals(1, accountManager.getExistingAccountNumbers().size()); + } + + @Test + void getAccountByAccountNumberShouldReturnCorrectAccount() { + AccountManager accountManager = new AccountManager(); + accountManager.addAccount("Test Account", 1000.00); + int accountNumber = accountManager.getAccount(0).getAccountNumber(); + assertEquals("Test Account", accountManager.getAccountByAccountNumber(accountNumber).getName()); + } + + @Test + void getAccountByInvalidAccountNumberShouldThrowException() { + AccountManager accountManager = new AccountManager(); + accountManager.addAccount("Test Account", 1000.00); + assertThrows(IllegalArgumentException.class, () -> accountManager.getAccountByAccountNumber(9999)); + } + + +} diff --git a/src/test/java/budgetbuddy/account/AccountTest.java b/src/test/java/budgetbuddy/account/AccountTest.java new file mode 100644 index 0000000000..4092972ac0 --- /dev/null +++ b/src/test/java/budgetbuddy/account/AccountTest.java @@ -0,0 +1,37 @@ +package budgetbuddy.account; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AccountTest { + + @Test + void creatingAccountWithValidParametersShouldSucceed() { + Account account = new Account(1234, "Test Account", 1000.00); + assertEquals(1234, account.getAccountNumber()); + assertEquals("Test Account", account.getName()); + assertEquals(1000.00, account.getBalance()); + } + + @Test + void settingBalanceShouldUpdateBalance() { + Account account = new Account(1234, "Test Account", 1000.00); + account.setBalance(2000.00); + assertEquals(2000.00, account.getBalance()); + } + + @Test + void settingNameShouldUpdateName() { + Account account = new Account(1234, "Test Account", 1000.00); + account.setName("Updated Account"); + assertEquals("Updated Account", account.getName()); + } + + @Test + void toStringShouldReturnCorrectFormat() { + Account account = new Account(1234, "Test Account", 1000.00); + String expectedString = " Account Number: 1234 | Name: Test Account | Balance: 1000.0"; + assertEquals(expectedString, account.toString()); + } +} diff --git a/src/test/java/budgetbuddy/categories/CategoryTest.java b/src/test/java/budgetbuddy/categories/CategoryTest.java new file mode 100644 index 0000000000..499f35704e --- /dev/null +++ b/src/test/java/budgetbuddy/categories/CategoryTest.java @@ -0,0 +1,49 @@ +package budgetbuddy.categories; + +import budgetbuddy.exceptions.InvalidCategoryException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CategoryTest { + @Test + public void testGetCategoryName() { + assertEquals("Dining", Category.DINING.getCategoryName()); + assertEquals("Groceries", Category.GROCERIES.getCategoryName()); + assertEquals("Utilities", Category.UTILITIES.getCategoryName()); + assertEquals("Transportation", Category.TRANSPORTATION.getCategoryName()); + assertEquals("Healthcare", Category.HEALTHCARE.getCategoryName()); + assertEquals("Entertainment", Category.ENTERTAINMENT.getCategoryName()); + assertEquals("Rent", Category.RENT.getCategoryName()); + assertEquals("Salary", Category.SALARY.getCategoryName()); + assertEquals("Others", Category.OTHERS.getCategoryName()); + } + + @Test + public void testGetCategoryNum() { + assertEquals(1, Category.DINING.getCategoryNum()); + assertEquals(2, Category.GROCERIES.getCategoryNum()); + assertEquals(3, Category.UTILITIES.getCategoryNum()); + assertEquals(4, Category.TRANSPORTATION.getCategoryNum()); + assertEquals(5, Category.HEALTHCARE.getCategoryNum()); + assertEquals(6, Category.ENTERTAINMENT.getCategoryNum()); + assertEquals(7, Category.RENT.getCategoryNum()); + assertEquals(8, Category.SALARY.getCategoryNum()); + assertEquals(9, Category.OTHERS.getCategoryNum()); + } + + @Test + public void testFromNumber() throws InvalidCategoryException { + assertEquals(Category.DINING, Category.fromNumber(1)); + assertEquals(Category.GROCERIES, Category.fromNumber(2)); + assertEquals(Category.UTILITIES, Category.fromNumber(3)); + assertEquals(Category.TRANSPORTATION, Category.fromNumber(4)); + assertEquals(Category.HEALTHCARE, Category.fromNumber(5)); + assertEquals(Category.ENTERTAINMENT, Category.fromNumber(6)); + assertEquals(Category.RENT, Category.fromNumber(7)); + assertEquals(Category.SALARY, Category.fromNumber(8)); + assertEquals(Category.OTHERS, Category.fromNumber(9)); + assertThrows(InvalidCategoryException.class, () -> Category.fromNumber(10)); + } +} diff --git a/src/test/java/budgetbuddy/parser/ParserTest.java b/src/test/java/budgetbuddy/parser/ParserTest.java new file mode 100644 index 0000000000..d55e57ffcc --- /dev/null +++ b/src/test/java/budgetbuddy/parser/ParserTest.java @@ -0,0 +1,61 @@ +package budgetbuddy.parser; +import budgetbuddy.account.Account; +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidAddTransactionSyntax; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.exceptions.InvalidEditTransactionData; +import budgetbuddy.exceptions.InvalidTransactionTypeException; +import org.junit.jupiter.api.Test; +import budgetbuddy.transaction.type.Transaction; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ParserTest { + + @Test + public void testParseTransaction() throws InvalidTransactionTypeException, EmptyArgumentException, + InvalidCategoryException, InvalidAddTransactionSyntax { + Parser parser = new Parser(); + Account account = new Account(1); + String input = "add /t/Income /n/Shopping /$/50 /d/14-03-2024 /c/1"; + Transaction transaction = parser.parseUserInputToTransaction(input , account); + assertEquals("Shopping", transaction.getDescription()); + assertEquals(50.0f, transaction.getAmount(), 0.001); + assertEquals(LocalDate.parse("14-03-2024", DateTimeFormatter.ofPattern("dd-MM-yyyy")), + transaction.getDate()); + assertEquals("Dining", transaction.getCategory().getCategoryName()); + } + + @Test + public void testParseTransactionType() throws InvalidEditTransactionData, InvalidEditTransactionData, + InvalidCategoryException { + Parser parser = new Parser(); + Account account = new Account(1); + + // Test case for valid income transaction + String incomeTransactionString = "income | test1 | 10-11-2022 | 1000.00 | 1"; + Transaction incomeTransaction = parser.parseEditTransaction(incomeTransactionString, account); + assertEquals("test1", incomeTransaction.getDescription()); + assertEquals(1000.00, incomeTransaction.getAmount(), 0.001); + assertEquals("Dining", incomeTransaction.getCategory().getCategoryName()); + assertEquals(LocalDate.parse("10-11-2022", DateTimeFormatter.ofPattern("dd-MM-yyyy")), + incomeTransaction.getDate()); + + // Test case for valid expense transaction + String expenseTransactionString = "expense | Grocery | 12-11-2022 | 50.00 | 2"; + Transaction expenseTransaction = parser.parseEditTransaction(expenseTransactionString, account); + assertEquals("Grocery", expenseTransaction.getDescription()); + assertEquals(-50.00, expenseTransaction.getAmount(), 0.001); + assertEquals("Groceries", expenseTransaction.getCategory().getCategoryName()); + assertEquals(LocalDate.parse("12-11-2022", DateTimeFormatter.ofPattern("dd-MM-yyyy")), + expenseTransaction.getDate()); + + } + + + + +} diff --git a/src/test/java/budgetbuddy/storage/DataStorageTest.java b/src/test/java/budgetbuddy/storage/DataStorageTest.java new file mode 100644 index 0000000000..7ce4310987 --- /dev/null +++ b/src/test/java/budgetbuddy/storage/DataStorageTest.java @@ -0,0 +1,76 @@ +package budgetbuddy.storage; + +import budgetbuddy.account.Account; +import budgetbuddy.categories.Category; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.transaction.type.Expense; +import budgetbuddy.transaction.type.Transaction; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + + +public class DataStorageTest { + @Test + public void testSaveTransactions() throws IOException, InvalidCategoryException { + DataStorage dataStorage = new DataStorage(); + ArrayList transactionArrayList = new ArrayList<>(); + Transaction t = new Expense(1, "test","Groceries", 50.0, + "25-03-2024", new Account(1)); + t.setCategory(Category.fromNumber(1)); + transactionArrayList.add(t); + ArrayList existingAccountNumbers = new ArrayList<>(); + existingAccountNumbers.add(1); + try { + dataStorage.readTransactionFile(existingAccountNumbers); + dataStorage.saveTransactions(transactionArrayList); + + File file = new File(DataStorage.TRANSACTIONS_FILE_PATH); + assertTrue(file.exists()); // Check if file exists after saving transactions + } catch (IOException e) { + fail("Exception thrown while saving transactions: " + e.getMessage()); + } finally { + FileWriter fw = new FileWriter(DataStorage.TRANSACTIONS_FILE_PATH, false); + } + } + + @Test + public void testReadFromFile() throws IOException, InvalidCategoryException { + DataStorage dataStorage = new DataStorage(); + ArrayList expectedTransactions = new ArrayList<>(); + Transaction t = new Expense( 1, "test","Groceries", 50.0, + "25-03-2024", new Account(1)); + t.setCategory(Category.fromNumber(1)); + expectedTransactions.add(t); + ArrayList existingAccountNumbers = new ArrayList<>(); + existingAccountNumbers.add(1); + dataStorage.readTransactionFile(existingAccountNumbers); + dataStorage.saveTransactions(expectedTransactions); + try { + ArrayList actualTransactions = dataStorage.readTransactionFile(existingAccountNumbers); + + // Check if read transactions match the expected transactions + assertEquals(expectedTransactions.size(), actualTransactions.size()); + for (int i = 0; i < expectedTransactions.size(); i++) { + assertEquals(expectedTransactions.get(i).getDescription(), actualTransactions.get(i).getDescription()); + assertEquals(expectedTransactions.get(i).getAmount(), + actualTransactions.get(i).getAmount(), 0.001); + assertEquals(expectedTransactions.get(i).getDate(), actualTransactions.get(i).getDate()); + assertEquals(expectedTransactions.get(i).getTransactionType(), + actualTransactions.get(i).getTransactionType()); + assertEquals(expectedTransactions.get(i).getCategory(), actualTransactions.get(i).getCategory()); + } + } catch (IOException e) { + fail("Exception thrown while reading from file: " + e.getMessage()); + } finally { + FileWriter fw = new FileWriter(DataStorage.TRANSACTIONS_FILE_PATH, false); + } + } +} diff --git a/src/test/java/budgetbuddy/transaction/TransactionListTest.java b/src/test/java/budgetbuddy/transaction/TransactionListTest.java new file mode 100644 index 0000000000..aadb24fe39 --- /dev/null +++ b/src/test/java/budgetbuddy/transaction/TransactionListTest.java @@ -0,0 +1,175 @@ +package budgetbuddy.transaction; + +import budgetbuddy.account.Account; +import budgetbuddy.account.AccountManager; +import budgetbuddy.categories.Category; +import budgetbuddy.exceptions.EmptyArgumentException; +import budgetbuddy.exceptions.InvalidAddTransactionSyntax; +import budgetbuddy.exceptions.InvalidCategoryException; +import budgetbuddy.exceptions.InvalidIndexException; +import budgetbuddy.exceptions.InvalidTransactionTypeException; +import budgetbuddy.transaction.type.Income; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import budgetbuddy.transaction.type.Transaction; + +import java.io.IOException; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TransactionListTest { + + private TransactionList transactionList; + private Account account; + private Account account2; + private AccountManager accountManager; + + @BeforeEach + public void setUp() throws IOException { + transactionList = new TransactionList(); + account = new Account(1); + account2 = new Account(2); + accountManager = new AccountManager(); + accountManager.getAccounts().add(account); // need to change this + } + + @Test + public void getTransactions_initiallyEmpty() { + assertEquals(0, transactionList.getTransactions().size()); + } + + @Test + public void processTransaction_addsTransaction() + throws InvalidTransactionTypeException, InvalidAddTransactionSyntax, + EmptyArgumentException, InvalidCategoryException { + Transaction testTransaction = new Income(1, "test","Test", 200, + "14-03-2024", + account); + testTransaction.setCategory(Category.fromNumber(1)); + transactionList.processTransaction("add /a/ 1 /t/Income /n/Test /$/200 /d/14-03-2024 /c/1", account); + + assertEquals(1, transactionList.getTransactions().size()); + assertEquals(testTransaction.getDescription(), transactionList.getTransactions().get(0).getDescription()); + assertEquals(testTransaction.getAmount(), transactionList.getTransactions().get(0).getAmount()); + assertEquals(testTransaction.getCategory().getCategoryName(), + transactionList.getTransactions().get(0).getCategory().getCategoryName()); + assertEquals(testTransaction.getDate(), transactionList.getTransactions().get(0).getDate()); + } + @Test + + public void processTransaction_withInvalidAddSyntax_throwsInvalidAddTransactionSyntax() { + + assertThrows(InvalidAddTransactionSyntax.class, () -> transactionList.processTransaction( + "add Expense /n/Shopping /$/50 /d/14-03-2024 /c/2", account)); + assertThrows(InvalidAddTransactionSyntax.class, () -> transactionList.processTransaction( + "add /t/Expense Shopping /$/50 /d/14-03-2024 /c/2", account)); + assertThrows(InvalidAddTransactionSyntax.class, () -> transactionList.processTransaction( + "add /t/Expense /n/Shopping 50 /d/14-03-2024 /c/2", account)); + assertThrows(InvalidAddTransactionSyntax.class, () -> transactionList.processTransaction( + "add /t/Expense /n/Shopping /$/50 14-03-2024 /c/2", account)); + } + + @Test + public void processTransaction_withInvalidTransactionType_throwsTransactionTypeException() { + + assertThrows(InvalidTransactionTypeException.class, () -> transactionList.processTransaction( + "add /a/ 1 /t/Donation /n/Test /$/200 /d/14-03-2024 /c/2", account)); + } + + @Test + public void removeTransaction_removesCorrectTransaction() throws EmptyArgumentException, + InvalidIndexException, InvalidCategoryException { + Transaction testTransaction1 = new Income(1, "test","Test1", 100, + "14-03-2024", account); + testTransaction1.setCategory(Category.fromNumber(1)); + Transaction testTransaction2 = new Income(1, "test","Test2", 200, + "16-03-2024", account); + testTransaction1.setCategory(Category.fromNumber(2)); + transactionList.addTransaction(testTransaction1); + transactionList.addTransaction(testTransaction2); + + transactionList.removeTransaction("delete 1", accountManager); + + assertEquals(1, transactionList.getTransactions().size()); + assertEquals(testTransaction2, transactionList.getTransactions().get(0)); + } + + @Test + public void removeTransaction_withInvalidIndex_throwsIndexOutOfBoundsException() { + Transaction testTransaction = new Income(1, "test","Test", 200, + "14-03-2024", account); + transactionList.addTransaction(testTransaction); + + assertThrows(InvalidIndexException.class, () -> transactionList.removeTransaction( + "delete 2", accountManager)); + } + + @Test + public void removeTransaction_withMissingIndex_throwsEmptyArgumentException() { + Transaction testTransaction = new Income(1, "test","Test", 100, + "14-03-2024", account); + transactionList.addTransaction(testTransaction); + + assertThrows(EmptyArgumentException.class, () -> transactionList.removeTransaction( + "delete", accountManager)); + } + + @Test + public void removeTransaction_withInvalidIndex_throwsNumberFormatException() { + Transaction testTransaction = new Income(1, "test","Test", 100, + "14-03-2024", account); + transactionList.addTransaction(testTransaction); + + assertThrows(NumberFormatException.class, () -> transactionList.removeTransaction( + "delete one", accountManager)); + } + + @Test + public void getAccountTransactions_filtersCorrectTransactions() throws InvalidCategoryException { + Transaction testTransaction1 = new Income(1, "test","Test1", 100, + "15-03-2024", account); + testTransaction1.setCategory(Category.fromNumber(1)); + Transaction testTransaction2 = new Income(1, "test","Test2", 200, + "16-03-2024", account); + testTransaction2.setCategory(Category.fromNumber(1)); + Transaction testTransaction3 = new Income(2, "test","Test3", 300, + "18-03-2024", account2); + testTransaction3.setCategory(Category.fromNumber(1)); + transactionList.addTransaction(testTransaction1); + transactionList.addTransaction(testTransaction2); + transactionList.addTransaction(testTransaction3); + + ArrayList accountTransactions; + accountTransactions = TransactionList.getAccountTransactions(transactionList.getTransactions(),1); + + assertEquals(2, accountTransactions.size()); + assertEquals(testTransaction1, accountTransactions.get(0)); + assertEquals(testTransaction2, accountTransactions.get(1)); + } + + @Test + public void getCategoryTransactions_filtersCorrectTransactions() throws InvalidCategoryException { + Transaction testTransaction1 = new Income(1, "test","Test1", 100, + "15-03-2024", account); + testTransaction1.setCategory(Category.fromNumber(1)); + Transaction testTransaction2 = new Income(1, "test","Test2", 200, + "16-03-2024", account); + testTransaction2.setCategory(Category.fromNumber(1)); + Transaction testTransaction3 = new Income(1, "test","Test3", 300, + "18-03-2024", account); + testTransaction3.setCategory(Category.fromNumber(5)); + transactionList.addTransaction(testTransaction1); + transactionList.addTransaction(testTransaction2); + transactionList.addTransaction(testTransaction3); + + ArrayList categoryTransactions; + categoryTransactions = TransactionList.getCategoryTransactions(transactionList.getTransactions(), + Category.fromNumber(1)); + + assertEquals(2, categoryTransactions.size()); + assertEquals(testTransaction1, categoryTransactions.get(0)); + assertEquals(testTransaction2, categoryTransactions.get(1)); + } +} diff --git a/src/test/java/budgetbuddy/transaction/type/ExpenseTest.java b/src/test/java/budgetbuddy/transaction/type/ExpenseTest.java new file mode 100644 index 0000000000..72fc46f1be --- /dev/null +++ b/src/test/java/budgetbuddy/transaction/type/ExpenseTest.java @@ -0,0 +1,48 @@ +package budgetbuddy.transaction.type; + +import budgetbuddy.account.Account; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ExpenseTest { + private Expense expense; + private Account account; + + @BeforeEach + void setUp() { + account = new Account(1, "Test Account", 0.0); + expense = new Expense(1, "Test Account", "Test Expense", 100.0, + "01-01-2023", account); + } + + @Test + void expenseDecreasesAccountBalance() { + assertEquals(-100.0, account.getBalance()); + } + + @Test + void expenseHasCorrectType() { + assertEquals("Expense", expense.getTransactionType()); + } + + @Test + void expenseWithNullDescriptionThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Expense(1, "Test Account", + null, 100.0, "01-01-2023", account)); + } + + @Test + void expenseWithEmptyDescriptionThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Expense(1, "Test Account", "", + 100.0, "01-01-2023", account)); + } + + @Test + void expenseWithNullAccountThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Expense(1, "Test Account", + "Test Expense", 100.0, "01-01-2023", null)); + } +} diff --git a/src/test/java/budgetbuddy/transaction/type/IncomeTest.java b/src/test/java/budgetbuddy/transaction/type/IncomeTest.java new file mode 100644 index 0000000000..e6ef40af8e --- /dev/null +++ b/src/test/java/budgetbuddy/transaction/type/IncomeTest.java @@ -0,0 +1,54 @@ +package budgetbuddy.transaction.type; + +import budgetbuddy.account.Account; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class IncomeTest { + private Income income; + private Account account; + + @BeforeEach + void setUp() { + account = new Account(1, "Test Account", 0.0); + income = new Income(1, "Test Account", "Test Income", 100.0, + "01-01-2023", account); + } + + @Test + void incomeIncreasesAccountBalance() { + assertEquals(100.0, account.getBalance()); + } + + @Test + void incomeHasCorrectType() { + assertEquals("Income", income.getTransactionType()); + } + + @Test + void incomeWithNegativeAmountThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Income(1, "Test Account", + "Test Income", -100.0, "01-01-2023", account)); + } + + @Test + void incomeWithNullDescriptionThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Income(1, "Test Account", + null, 100.0, "01-01-2023", account)); + } + + @Test + void incomeWithEmptyDescriptionThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Income(1, "Test Account", "", + 100.0, "01-01-2023", account)); + } + + @Test + void incomeWithNullAccountThrowsAssertionError() { + assertThrows(AssertionError.class, () -> new Income(1, "Test Account", + "Test Income", 100.0, "01-01-2023", null)); + } +} diff --git a/src/test/java/budgetbuddy/transaction/type/TransactionTest.java b/src/test/java/budgetbuddy/transaction/type/TransactionTest.java new file mode 100644 index 0000000000..73fab1d786 --- /dev/null +++ b/src/test/java/budgetbuddy/transaction/type/TransactionTest.java @@ -0,0 +1,78 @@ +package budgetbuddy.transaction.type; + +import budgetbuddy.account.Account; +import budgetbuddy.categories.Category; +import budgetbuddy.exceptions.InvalidCategoryException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TransactionTest { + + private Account account; + + @BeforeEach + public void setUp() { + account = new Account(1); + } + + @Test + public void testTransactionConstructor() { + Transaction transaction = new Income(1, "test","Groceries", + 50.0f, "14-03-2024", new Account(1)); + assertEquals("Groceries", transaction.getDescription()); + assertEquals(50.0f, transaction.getAmount(), 0.001); + LocalDate date = LocalDate.parse("14-03-2024", DateTimeFormatter.ofPattern("dd-MM-yyyy")); + assertEquals(date, transaction.getDate()); + } + + @Test + public void testGetDescription() { + Transaction transaction = new Income(1, "test","Groceries", + 50.0f, "14-03-2024", + account); + assertEquals("Groceries", transaction.getDescription()); + } + + @Test + public void testGetAmount() { + Transaction transaction = new Income(1, "test","Groceries", + 50.0f, "14-03-2024", + account); + assertEquals(50.0f, transaction.getAmount(), 0.001); + } + + @Test + public void testGetCategory() throws InvalidCategoryException { + Transaction transaction = new Income(1, "test","Groceries", + 50.0f, "14-03-2024", + account); + transaction.setCategory(Category.fromNumber(1)); + assertEquals("Dining", transaction.getCategory().getCategoryName()); + } + + @Test + public void testGetDate() { + Transaction transaction = new Income(1, "test","Groceries", + 50.0f, "14-03-2024", + account); + LocalDate date = LocalDate.parse("14-03-2024", DateTimeFormatter.ofPattern("dd-MM-yyyy")); + assertEquals(date, transaction.getDate()); + } + + @Test + public void testToString() throws InvalidCategoryException { + Transaction transaction = new Income(1, "test","Groceries", + 50.0f, "14-03-2024", + account); + transaction.setCategory(Category.fromNumber(1)); + String expected = " Account Number: 1 | Account Name: test | Transaction Type: Income |" + + " Description: Groceries | Date: 2024-03-14 | Amount: 50.0 | " + + " Category: Dining"; + assertEquals(expected, transaction.toString()); + } +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..61619e10e3 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,6 @@ Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +BUDGET BUDDY +What can I do for you? +---------------------------------------------------------------------------------------------------------------------------------------- + Bye... Don't forget to keep track of your future transactions +---------------------------------------------------------------------------------------------------------------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..b023018cab 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1 @@ -James Gosling \ No newline at end of file +bye