diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fd8c44d086..391c46b4fe 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -32,19 +32,3 @@ 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 diff --git a/.gitignore b/.gitignore index 2873e189e1..52cc8a9464 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +data/data.txt +..gitignore.un~ +config/.idea/ +/*.class \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..c5f3f6b9c7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..3748942f1e --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: financeproject.Main + diff --git a/README.md b/README.md index f82e2494b7..53fcf874a7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# Finance Calculator This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. diff --git a/build.gradle b/build.gradle index ea82051fab..08d9ce4e4d 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' } + test { useJUnitPlatform() @@ -29,11 +30,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("financeproject.Main") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("project") archiveClassifier.set("") } @@ -43,4 +44,7 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } + + diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000000..9cbc1bc6b4 --- /dev/null +++ b/compile.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Define the base directory variables +SRC_DIR="src" +BUILD_DIR="build" +CLASSES_DIR="$BUILD_DIR/classes" +LIBS_DIR="$BUILD_DIR/libs" +JAR_NAME="Financer.jar" + + +# Compile the Java files +find "$SRC_DIR" -name "*.java" ! -path "*/test/*" > sources.txt +javac -d "$CLASSES_DIR" @sources.txt +rm sources.txt + +# Package the .class files into a .jar file +cd "$CLASSES_DIR" +jar cvf "../libs/$JAR_NAME" * +cd - + +echo "Compilation and packaging complete." diff --git a/data/Bob.txt b/data/Bob.txt new file mode 100644 index 0000000000..b1081d210c --- /dev/null +++ b/data/Bob.txt @@ -0,0 +1,4 @@ +0.00 +Stocks|400.00|Oct 24 2024 12:00PM|INVESTMENT|I +Groceries|-150.00|Jun 23 2023 06:30PM|SHOPPING|O +Refund|100.00|Jun 23 2023 05:00PM|REFUND|I diff --git a/data/data.txt b/data/data.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/passwords.txt b/data/passwords.txt new file mode 100644 index 0000000000..962ea2afd5 --- /dev/null +++ b/data/passwords.txt @@ -0,0 +1 @@ +Bob|password \ No newline at end of file diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..ee702d1638 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,8 @@ # 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 +--------|:----------:|:-----------------------------------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Dylan | [Github](https://github.com/dylansiew) | [Portfolio](./team/dylansiew.md) +![](https://via.placeholder.com/100.png?text=Photo) | Kishen | [Github](https://github.com/Kishen271828) | [Portfolio](./team/kishen271828.md) +![](https://via.placeholder.com/100.png?text=Photo) | Chong Xern | [Github](https://github.com/ChongXern) | [Portfolio](./team/chongxern.md) +![](https://via.placeholder.com/100.png?text=Photo) | Chen How | [Github](https://github.com/chenhowy) | [Portfolio](./team/chenhowy.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..a9ed243226 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -3,36 +3,148 @@ ## Acknowledgements {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- [CS2113 Course Website](https://nus-cs2113-ay2324s2.github.io/website/index.html) ## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +![java](https://github.com/AY2324S2-CS2113-F14-4/tp/assets/88567790/2588478b-65bd-4e65-a295-6bc96ee6c23b) + + +The financial manager application is designed using an object-oriented approach, focusing on user authentication, transaction management, and user interaction. The core components include: + +- **Main Application Loop**: Handles the initialization of the application, including loading data from storage, authenticating the user, and processing user commands until the application exits. +- **Command Pattern**: User commands are encapsulated as objects, allowing for the addition of new commands with minimal changes to existing code. +- **Transaction Management**: Separate classes for managing inflows and outflows, with a unified interface for adding, editing, and deleting transactions. +- **User Authentication**: Ensures that users are authenticated before accessing their financial data, with a mechanism to track and limit failed attempts. +- **Inactivity Timer**: Automatically logs out users after a period of inactivity, enhancing security. +- **Storage Management**: Handles the persistent storage of transaction data, allowing users to save and load their financial information. +- **[Proposed] Security features**: A maximum of three login attempts are inputted, failing which would cause the application to be exited automatically. +- **[Proposed] Financial transaction visualiser**: When viewing history of transactions, the data will be visualised using bar charts for better user friendliness. +- **[Proposed] Undo functionality**: Allows users to undo their last prompt, but only permittable 10 seconds after the last action. + +### View Transaction History feature: + +#### Implementation Details: + +#### Functionality: +The View History feature allows users to retrieve a list of their recent transactions, categorized as inflows and outflows. The user can specify the number of transactions they want to view, and the system presents these transactions in reverse chronological order, with the latest transactions displayed first. Each transaction is presented with its description, date, and time of occurrence. + +#### Design Considerations: +- **Data Structure**: The transaction data is stored in an instance of the `TransactionList` class, which is part of the `financialtransactions` package. This list maintains a record of all transactions made by the user, ensuring easy retrieval and manipulation of transaction data. +- **Categorization**: Transactions are categorized as inflow or outflow based on their type. This categorization enables users to differentiate between different types of financial activities, such as income and expenses. +- **Formatting**: The transactions are presented in a formatted string, providing clear and concise information to the user. Each transaction includes its description, date, and time. + +#### Implementation Details: +- The `TransactionManager#showLastNTransactions()` method is responsible for retrieving the last N transactions from the Transaction List. +- The method iterates over the transaction list in reverse order, starting from the latest transaction and moving towards older transactions. +- Transactions are categorized as inflows or outflows based on their instance type (Inflow or Outflow), and the relevant transactions are added to the output string accordingly. +- The formatted string containing the transaction details is returned to the user for display. + +#### Alternatives Considered: +During the development of the View Transaction History feature, several alternatives were considered to achieve the desired functionality. These alternatives included: + +- Implementing a more complex data structure for storing transaction history, such as a linked list. However, a simple ArrayList-based approach was chosen for its simplicity and efficiency in managing transaction data. +- Providing additional filtering options, such as filtering transactions by date range or transaction category. While these options could definitely enhance the feature, they were deemed unnecessary for the initial implementation and may be considered for future iterations. + +### Save File feature: +The `Storage#saveFile()` method will be called when the user decides to quit the application, the method will then get a string of all the transactions. This string will then be written to a file whose filename will be the username of the last user. + +#### Implementation Details: +- The method first creates a FileWriter object. +- It then calls `TransactionManager#toSave()`. + - This method is similar to `toString()`, except that it creates a string that is more storage friendly. + - In this method, each transaction is returned as `name|amount|date|category\n`. + - Each transaction is added line by line, after adding all transactions, the string is returned in the toSave() method. + - It is important to note that outflows are saved with a `-` in front of the amount. +- With the string generated in the `toSave()` method, the FileWriter will write it to the file before closing it. + +### Load File feature: +The `Storage#loadFile()` method will be called after the user as logged in, it will then retrieve the transactions that were written in the past sessions, so that the user can continue using the finance manger. + +#### Implementation Details: +- The method first creates a File object and a Transaction Manager object. +- If the file exists + - The method will read from the file line by line, splitting each line by `|`. A new inflow or outflow object will be created with those values and then added to the Transaction Manager Object. +- If the file does not exist + - An empty transaction manager object will be returned. +- To differentiate between inflow or outflow, the `-` in front of the amount will be used. + + +![photo_2024-04-04 23 54 55](https://github.com/AY2324S2-CS2113-F14-4/tp/assets/88567790/aa2df04e-c493-443e-a4f1-e3cfc506e56a) +![photo_2024-04-04 23 54 54](https://github.com/AY2324S2-CS2113-F14-4/tp/assets/88567790/95585373-a8a8-4d12-bfce-80cde6f57ebc) +![photo_2024-04-04 23 54 53](https://github.com/AY2324S2-CS2113-F14-4/tp/assets/88567790/64698f02-9acd-42b5-a8d6-1cbc22574a05) ## Product scope ### Target user profile -{Describe the target user profile} +The financial manager application is designed for individual users seeking a simple yet powerful tool to manage personal finances, including tracking income and expenses, and viewing transaction history. + ### Value proposition -{Describe the value proposition: what problem does it solve?} +This application simplifies personal financial management by providing an intuitive interface for tracking and analyzing income and expenses. Users can easily add, edit, or delete transactions, view recent transaction history, and ensure their data is securely managed with user authentication and automatic logout features. + ## 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/an ... | I want to ... | So that ...| +|-------|-------------|---------------|------------| +|v1.0|user|receive alerts or notifications when I exceed my budget limits for specific expense categories|I can stay within my financial goals| +|v1.0|user|generate monthly reports summarizing my income, expenses, and budget performance|I can track my financial progress over time| +|v1.0|user|edit or delete past transactions|I can correct any errors or update information as needed| +|v1.0|user|search for specific transactions based on keywords, dates, or categories|I can quickly find the information I need| +|v1.0|user|add income transactions with details such as amount, date, and category|I can keep track of my earnings| +|v1.0|user|add expense transactions with details such as amount, date, and category|I can monitor where my money is going| +|v1.0|user|categorize my transactions|I can organize my finances and have a clearer view of my income and expenses| +|v1.0|user|add instalment payments into the tracker|I can track finances for big ticket purchases such as furniture/TVs| +|v1.0|user|Use a customisable interface for my financial goals|I can adjust these financial goals accordingly as time passes| +|v1.0|user|add payment types such as credit card, debit card or cash|I can be reminded to pay credit card bills each month| +|v1.0|user|Be confident that my banking information is encrypted and safe from being accessed by others|The private information is not easily leaked to others| +|v2.0|user|Export my financial reports as a CSV|I can share my expenditure with my peers| +|v2.0|user|Visualise my transactions in more meaningful diagrams|I can better understand my spending| ## Non-Functional Requirements -{Give non-functional requirements} +1. **Security**: User authentication must be secure, with a limit on login attempts to prevent unauthorized access. +2. **Usability**: The application should be easy to use, with clear instructions and feedback for users. +3. **Performance**: The application should respond quickly to user inputs, with minimal delays in processing transactions. +4. **Reliability**: Data storage should be reliable, ensuring that user data is not lost between sessions. + ## Glossary -* *glossary item* - Definition +1. **Inflow** - A financial transaction representing income or money received. +2. **Outflow** - A financial transaction representing expenses or money spent. + ## 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} +### Login Procedure: +- Start the application and enter your username and password when prompted. +- Test incorrect passwords to ensure the application correctly limits login attempts. +- [v2.0] If there are three incorrect attempts, the application will automatically exit and the user has to try again. + +### Adding Transactions: +- Use the `add-inflow` and `add-outflow` commands to add new transactions, following the command format provided in the `help` command. +- Attempt to add transactions with missing or incorrect information to test validation. + +### Editing and Deleting Transactions: +- Use the `edit-inflow` and `edit-outflow` commands to modify existing transactions. +- Use the `delete-inflow` and `delete-outflow` commands to remove transactions. +- Try editing or deleting transactions that do not exist to test error handling. + +### Viewing Transaction History: +- Use the `view-history` command to display recent transactions. +- Test with different numbers of transactions to view. +- [v2.0] Additional bar charts will be shown to display the different percentage of categories used in the past n transactions. + +### Inactivity Timeout: +- After logging in, do not input any commands for the duration specified by the inactivity timer to test automatic logout. +- A maximum of 2.5 minutes of inactivity will trigger the application to check with the user whether the user is still active or not. Input `yes` to indicate continued activity, otherwise input `no` to exit. +- 30 seconds after the 2.5 min (3 min), the application will automatically exit. +- Implemented to provide enhanced security, so no third party can access finance transactions. + + +### Data Persistence: +- Exit the application and restart it to ensure that previously entered transactions are still present. diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..a6f8f73ede 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# FinTrack -{Give product intro here} +FinTrack is a desktop app designed for individuals who want to manage their finances using a Command Line Interface (CLI). FinTrack offers a convenient way for users to track their income, expenses, and budgets through typed commands. With FinTrack, users can easily add income and expense transactions, categorize them, set budgets, and generate reports to gain valuable insights into their financial activities. Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..6ab4ae2ef1 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,266 @@ -# User Guide +# FinTrack User Guide ## Introduction -{Give a product intro} +FinTrack is a desktop app designed for individuals who want to manage their finances using a Command Line Interface (CLI). FinTrack offers a convenient way for users to track their income, expenses, and budgets through typed commands. With FinTrack, users can easily add income and expense transactions, categorize them, set budgets, and generate reports to gain valuable insights into their financial activities. ## Quick Start -{Give steps to get started quickly} - 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +2. Download the latest version of `FinTrack.jar` from the latest milestone in our product website. +3. Copy the file to the folder you want to use as the home folder for your FinTrack app. +4. Open a command terminal, navigate to the folder containing the jar file, and run the command `java -jar FinTrack.jar` to run the application. +5. Upon starting FinTrack, you will first be prompted to type the username and password. The username is **Bob** and the password is **password** + +## Features +- [Logging in into user session](#logging-in-into-user-session) +- [Viewing help](#viewing-help-help) +- [View Transaction History](#view-transaction-history-view-history) +- [Add Inflow](#add-inflow-add-inflow) +- [Delete Inflow](#delete-inflow-delete-inflow) +- [Add Outflow](#add-outflow-add-outflow) +- [Delete Outflow](#delete-outflow-delete-outflow) +- [Add Reminder](#add-reminder-add-reminder) +- [Delete Reminder](#delete-reminder-delete-reminder) +- [Edit Inflow](#edit-inflow-edit-inflow) +- [Edit Outflow](#edit-outflow-edit-outflow) +- [Edit Reminder](#edit-reminder-edit-reminder) +- [Undo last action](#undo-last-action-undo) +- [Set budget](#set-a-budget-set-budget) +- [Generate report](#generating-a-report-generate-report) +- [Exit Program](#exiting-the-program-quit) +- [Saving Data](#saving-the-data) +- [Inactivity Timer](#inactivity-timer) + + +### Logging in into user session: + +- Securely logs the user into their Finance Manager session. + +### Viewing Help: `help` + +- Shows a message explaining the different commands available and their formats. + +Format: `help` + +### View Transaction History: `view-history` + +- Displays the last n transactions made. +- Optional `w/chart` can be added at the end to visualise transaction history with bar charts. +- `all` tag allows the user to view all transactions made. + +Format: `view-history n/NUM [w/CHART]` + +Examples: +- `view-history n/20 w/chart` +- `view-history all` + +### Add Inflow: `add-inflow` + +- Adds a new inflow of money to your financial records. +- `NAME` should not have any white space characters in it. +- Date input is in DD/MM/YYYY or DD-MM-YYYY format. Time input is 24-hour format. +- Only the following categories are allowed: *INCOME, INVESTMENT, GIFT, LOAN, REFUND, OTHER* +- The input parameters can be in any order + +Format: `add-inflow n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` + +Example: +- `add-inflow n/Salary a/5000 d/21/02/2024 t/1700 c/income` +- `add-inflow n/Birthday c/gift d/21-03-2023 t/1200 a/50` + +### Delete Inflow: `delete-inflow` + +- Checks for and deletes existing entry of inflow from financial record. +- Before deleting, the list of inflows along with their indices can be viewed using the `view-history` command. + +Format: `delete-inflow i/INDEX` + +Example: `delete-inflow i/2` + +### Add Outflow: `add-outflow` + +- Adds a new outflow of money to your financial records. +- `NAME` should not have any white space characters in it. +- Date input is in DD/MM/YYYY or DD-MM-YYYY format. Time input is 24-hour format. +- Only the following categories are allowed: *FOOD, RENT, DEBT, SHOPPING, TREAT, EDUCATION, TAX, OTHER* +- The input parameters can be in any order + +Format: `add-outflow n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` + +Example: +- `add-outflow n/John a/100 d/19-02-2024 t/1600 c/treat` +- `add-outflow n/Bob a/600 c/treat t/1600 d/19/02/2024` + +### Delete Outflow: `delete-outflow` + +- Checks for and deletes existing entry of outflow from financial record. +- Before deleting, the list of outflows along with their indices can be viewed using the `view-history` command. + +Format: `delete-outflow i/INDEX` + +Example: `delete-outflow i/5` + +### Add Reminder: `add-reminder` + +- Adds a new reminder of upcoming payment to your financial records. +- `NAME` should not have any white space characters in it. +- Date input is in DD/MM/YYYY or DD-MM-YYYY format. Time input is 24-hour format. +- Take note that the date entered must be in the future. +- Only the following categories are allowed: *INSTALLMENT, CREDITCARD, UTILITIES, OTHER* +- The input parameters can be in any order + +Format: `add-reminder n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` + +Example: +- `add-reminder n/water_bills a/35 d/21/06/2024 t/1200 c/UTILITIES` +- `add-reminder a/45 d/21/06/2024 t/1200 n/electricity_bills c/UTILITIES` + +### Delete Reminder: `delete-reminder` + +- Checks for and deletes existing entry of reminder from financial record. +- Before deleting, the list of reminders along with their indices can be viewed using the `view-history` command. + +Format: `delete-reminder i/INDEX` + +Example: `delete-reminder i/5` -## Features +### Edit Inflow: `edit-inflow` -{Give detailed description of each feature} +- Edits an existing entry of inflow from your financial records. +- Before editing, the list of inflows along with their indices can be viewed using the `view-history` command. +- `NAME` should not have any white space characters in it. +- Date input is in DD/MM/YYYY or DD-MM-YYYY format. Time input is 24-hour format. +- Only the following categories are allowed: *INCOME, INVESTMENT, GIFT, LOAN, REFUND, OTHER* +- Input the index you want to edit and provide the details you want to update for the inflow. +- The input parameters can be in any order -### Adding a todo: `todo` -Adds a new item to the list of todo items. +Format: `edit-inflow i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` -Format: `todo n/TODO_NAME d/DEADLINE` +Example: +- `edit-inflow i/7 n/Salary a/5000 d/21/02/2024 t/1700 c/income` +- `edit-inflow n/Stocks a/5000 d/27-02-2024 t/1700 c/investment i/4` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +### Edit Outflow: `edit-outflow` -Example of usage: +- Edits an existing entry of outflow from your financial records. +- Before editing, the list of outflows along with their indices can be viewed using the `view-history` command. +- `NAME` should not have any white space characters in it. +- Date input is in DD/MM/YYYY or DD-MM-YYYY format. Time input is 24-hour format. +- Only the following categories are allowed: *FOOD, RENT, DEBT, SHOPPING, TREAT, EDUCATION, TAX, OTHER* +- Input the index you want to edit and provide the details you want to edit for the outflow. +- The input parameters can be in any order -`todo n/Write the rest of the User Guide d/next week` +Format: `edit-outflow i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +Example: +- `edit-outflow i/6 n/John a/100 d/19/02/2024 t/1600 c/food` +- `edit-outflow i/3 n/Mary a/150 c/debt d/12-04-2024 t/1600` + +### Edit Reminder: `edit-reminder` + +- Edits an existing entry of reminder in your financial records. +- Before editing, the list of reminders along with their indices can be viewed using the `view-history` command. +- `NAME` should not have any white space characters in it. +- Date input is in DD/MM/YYYY or DD-MM-YYYY format. Time input is 24-hour format. +- Only the following categories are allowed: *INSTALLMENT, CREDITCARD, UTILITIES, OTHER* +- Input the index you want to edit and provide the details you want to edit for the outflow. +- The input parameters can be in any order + +Format: `edit-reminder i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` + +Example: +- `edit-reminder i/2 n/water_bills a/35 d/21/06/2024 t/1200 c/UTILITIES` +- `edit-reminder i/12 n/income_tax a/2000 d/21-07-2014 t/1100 c/other` + +### Undo last action: `undo` +- This command will undo the last command inputted by the user. +- Only applicable for the commands: + - `add-inflow` + - `add-outflow` + - `add-reminder` + - `delete-inflow` + - `delete-outflow` + - `delete-reminder` + +Format: `undo` + +### Set a budget `set-budget` +- Sets a per-month budget from a default of $0.00. + +Format: `set-budget a/AMOUNT` + +Examples of usage: + +`set-budget a/2000.00` + +### Generating a report `generate-report` +- Generates a report of a certain month. +- Take note that MMM is the 3-letter abbreviation of the month in all-caps. + +Format: `generate-report m/MMM y/YYYY` + +Examples of usage: + +`generate-report m/JUN y/2023` + +`generate-report m/MAR y/2024` + +### Exiting the Program: `quit` + +- Quits from the FinTrack app. + +Format: `quit` + +### Saving the Data + +FinTrack data is saved in the hard disk automatically when the user exits the program. There is no need to save the data manually. + +### Inactivity Timer + +FinTrack allows a maximum of 3 minutes of inactivity on the software, then it will shut down by itself. However, a grace period of 30 seconds is offered to the user, whereby the user is asked whether there will be any further activity or not. ## 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 on the other computer and overwrite the empty data file it creates with the file that contains the data of your previous FinTrack home folder. -## Command Summary +**Q**: Can I customize the categories for transactions in FinTrack? + +**A**: Currently, FinTrack comes with predefined categories for both inflow and outflow transactions. Custom categories are not supported in the current version, but we're always looking to improve and may include this feature in future releases. + +**Q**: Is it possible to have multiple user accounts on FinTrack? + +**A**: FinTrack is designed to be used by a single user, and it does not support multiple user accounts in its current version. If multiple users wish to use FinTrack, they would need to install it in separate directories or use different machines. + +**Q**: What should I do if I encounter an error or a bug? -{Give a 'cheat sheet' of commands here} +**A**: If you encounter any errors or bugs while using FinTrack, please report them to our support team. You can do this by creating an issue on our GitHub repository. Please provide detailed information about the issue and steps to reproduce it, if possible. + +**Q**: Can FinTrack be used on mobile devices? + +**A**: FinTrack is a desktop application designed for use on computers with Java 11 or above. It is not currently available for mobile devices. However, accessing the application via remote desktop software might be a workaround for accessing FinTrack on a mobile device. + +**Q**: How secure is my financial data with FinTrack? + +**A**: Your financial data is stored locally on your device and is not transmitted over the internet. We recommend keeping your device secure and regularly backing up your FinTrack data file to prevent data loss. Ensure your device has adequate security measures, such as updated antivirus software and a strong login password. + +**Q**: Can I export FinTrack data for use in other applications? + +**A**: FinTrack does not currently support exporting data directly to other financial software or applications. However, you can manually input the data into other applications if they support similar transaction formats. + +## Command Summary -* Add todo `todo n/TODO_NAME d/DEADLINE` +* View Help: `help` +* View Transaction History: `view-history n/NUM [w/CHART]` +* Add Inflow: `add-inflow n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` +* Delete Inflow: `delete-inflow i/INDEX` +* Edit Inflow: `edit-inflow i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` +* Add Outflow: `add-outflow n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` +* Delete Outflow: `delete-outflow i/INDEX` +* Edit Outflow: `edit-outflow i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY` +* Undo: `undo` +* Set budget: `set-budget a/AMOUNT` +* Generate report: `generate-report m/MMM y/YYYY` +* Exit Program: `quit` \ No newline at end of file diff --git a/docs/team/chenhowy.md b/docs/team/chenhowy.md new file mode 100644 index 0000000000..544c5edc7e --- /dev/null +++ b/docs/team/chenhowy.md @@ -0,0 +1,47 @@ +# Yong Chen How - Project Portfolio Page + +## Overview +Finance tracking application that helps keep track of all things related to finances such as income, spending, expenditure, reminders, gifts, and more. + +### Summary of Contributions +[**Code contributed**](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=chenhowy&tabRepo=AY2324S2-CS2113-F14-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) by Chen How + +#### Enhancements implemented +1. **Storage class** + + The storage class deals with loading and storing all user data. + - Loading user data when app launches. + - Creating file directories if they do not exist. + - Storing user data after each user action/ user quits. + - Also created a create user method to allow the app to handle multiple users. However, this was later unused as it was out of project scope. + + +2. **Created reminder class** + + This class is similar to both inflows and outflows, however the main distinction is that reminders are for transactions that have yet to happen. + - This class was extended from both inflows and outflows, which was not written by myself. + + +3. **Generate report** + + There are two forms of generate report: generateReport and generateQuickReport. + - generateQuickReport is only used when app is launched and after user has logged in. It gets a quick overview of the spendings in the past 1 month. + - generateReport is called when the user wants to check any month that has already passed. It gets a full overview comparing inflows and outflows, so that the user can see their comprehensive spendings. + + +4. **Budget** + Budget keeps track of total amount the user sets to spend each month. When logging in, the user is reminded of the budget they have left to spend. + - Budget is currently just a variable under Transaction Manger. + - It could be more fleshed out as a full class on its own to have more comprehensive features such as having different budgets each month and having separate categories. + +5. **Help command** + This command generates a simple string which consists of all commands available for the user to use. + +#### Contributions to user guide +Added personal contributions such as commands for budget, generate report and reminder class. + +#### Contributions to developer guide +Added personal contributions such as commands for budget, generate report and reminder class. + +#### Contributions to team-based tasks +Helped with debugging, especially when teammates faced certain issues with their build. \ No newline at end of file diff --git a/docs/team/chongxern.md b/docs/team/chongxern.md new file mode 100644 index 0000000000..e4918ae1b5 --- /dev/null +++ b/docs/team/chongxern.md @@ -0,0 +1,29 @@ +# Chong Xern - Project Portfolio Page + +## Overview +Finance tracking application that helps keep track of all things related to finances such as income, spending, expenditure, reminders, gifts and more. + +### Summary of Contributions +[**Code Contributed**](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=ChongXern&tabRepo=AY2324S2-CS2113-F14-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false/) by Chong Xern +#### Enhancements Implemented +1. **Undo last action functionality** + +Allow actions such as `add-inflow` and `delete-outflow` to be undone using the `undo` command. + +2. **Bar chart feature for** `view-history` + +Enable descriptive bar chart of transactions to be viewed using `w/chart` + +3. **Inactivity Timer** + +Used to check that the user is still inputting commands or is inactive. After 2.5 min the application will check for availability, and after 3 min the application will shut down by itself. + +4. **Bug fixes** + +5. **JUnit testing** + +#### Contributions to User Guide +Added personal contributions to my enhancements, as well as adding more information to other functionalities and improving the formatting of the User Guide with hyperlinks to sections. + +#### Contributions to Developer Guide +Added relevant descriptions, information and other amendments to enhancments and user guides, diff --git a/docs/team/dylansiew.md b/docs/team/dylansiew.md new file mode 100644 index 0000000000..dda016d8b5 --- /dev/null +++ b/docs/team/dylansiew.md @@ -0,0 +1,69 @@ +# Dylan Siew - Project Portfolio Page + +## Overview +Finance tracking application that helps keep track of all things related to finances such as income, spending, expenditure, reminders, gifts, and more. + +### Summary of Contributions +[**Code Contributed**](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=dylansiew&tabRepo=AY2324S2-CS2113-F14-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) by Dylan Siew + +#### Enhancements Implemented + +1. **Create Base User Class** + + Implemented the foundation for user data handling, enabling personalized user experiences and data management. + +2. **Create Authentication Class** + + Developed authentication mechanisms to ensure user data security and privacy, involving password protection and secure user login. + +3. **Create Date Handler Class** + + Designed a class to manage and parse date information, essential for scheduling and tracking financial transactions over time. + +4. **Create Expenditure Class** + + Established a class to track and manage user expenditures, providing a structured way to record and analyze spending. + +5. **Create Base Command Class** + + Crafted the core command class structure to streamline the processing and execution of user commands, enhancing the application's scalability and maintainability. + +6. **Create Add Source/Transaction Command Class** + + Devised functionality for users to add income sources and financial transactions, enriching the application's capability to monitor financial flows. + +7. **Create Delete Source/Transaction Command Class** + + Formulated a method for users to remove unwanted or incorrect income sources and transactions, maintaining the accuracy and relevance of financial data. + +8. **Ensure UI Object Used in Printing of All Statements** + + Refined the application's user interface by standardizing the output mechanism, improving user interaction consistency and clarity. + +#### Contributions to User Guide + +In the User Guide, I focused on expanding the Q&A section to address common queries and concerns users might have regarding FinTrack. My contributions aimed to clarify the application's capabilities, usage, and security aspects, enhancing user understanding and confidence in using FinTrack: + +1. **Customization of Transaction Categories**: I clarified that FinTrack currently does not support custom transaction categories but mentioned the possibility of including this feature in future versions, reflecting the application's commitment to improvement based on user feedback. + +2. **Multiple User Accounts**: I addressed the limitation of FinTrack in supporting only single user accounts and suggested workarounds for multiple users wishing to use the application, guiding users towards practical solutions for shared use. + +3. **Reporting Errors or Bugs**: I provided guidance on how users can report errors or bugs they encounter, emphasizing the importance of detailed information and reproduction steps. This contribution is crucial for maintaining an open channel of communication with users and facilitating continuous improvement of FinTrack. + +4. **Mobile Device Compatibility**: I explained that FinTrack is primarily a desktop application and outlined potential workarounds for accessing it on mobile devices through remote desktop software, offering users flexibility in how they access the application. + +5. **Security of Financial Data**: I reassured users about the security of their financial data with FinTrack, emphasizing local data storage and the importance of device security and data backup practices. This response aims to build user trust in the application's data handling. + +6. **Data Export Capabilities**: I clarified the current limitations regarding data export to other applications, while suggesting manual data entry as an interim solution, indicating an area for potential development in future versions of FinTrack. + +#### Contributions to Developer Guide + +1. **Main Application Loop**: I documented the initialization process of the application, emphasizing the sequence of operations from loading data, user authentication, to handling user commands. This section provides insight into the application's lifecycle and user interaction flow. + +2. **Command Pattern**: My contribution highlighted how user commands are encapsulated as objects, facilitating the easy addition of new commands. This approach minimizes changes to existing code, showcasing the application's extensibility and maintainability. + +3. **Transaction Management**: I elaborated on the implementation of separate classes for managing inflows and outflows, including a unified interface for transaction operations such as addition, editing, and deletion. This part of the guide explains how the application organizes and processes financial data. + +4. **User Authentication**: I detailed the authentication process, ensuring that users are securely authenticated before accessing their financial data. This includes mechanisms to track and limit failed login attempts, reinforcing the application's security measures. + +5. **Storage Management**: My documentation covered how the application handles the persistent storage of transaction data, allowing for the secure saving and loading of user financial information across sessions. This ensures data reliability and availability. diff --git a/docs/team/kishen271828.md b/docs/team/kishen271828.md new file mode 100644 index 0000000000..ad121e96a6 --- /dev/null +++ b/docs/team/kishen271828.md @@ -0,0 +1,55 @@ +# Kishen Gandhi - Project Portfolio Page + +## Overview +FinTrack is a desktop app designed for individuals who want to manage their finances using a Command Line Interface (CLI). FinTrack offers a convenient way for users to track their income, expenses, and budgets through typed commands. With FinTrack, users can easily add income and expense transactions, categorize them, set budgets, and generate reports to gain valuable insights into their financial activities. + +### Summary of Contributions + +[**Code Contributed**](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=kishen271828&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) by Kishen Gandhi + +#### Enhancements Implemented + +1. **Add, Delete and Edit functionality for Inflows** + + Implemented add, delete, and edit functionalities for managing inflows in the finance tracker + + +2. **Add, Delete and Edit functionality for Outflows** + + Implemented add, delete, and edit functionalities for managing outflows in the finance tracker + + +3. **View Transaction History feature** + + Initially, this feature was implemented such that users can view their past transactions. This feature was further enhanced by Chong Xern allowing users to visualize the transaction history in the form of a bar chart. + + +4. **Sort Transactions by Time** + + Implemented sorting of transactions by date, ensuring transactions are displayed chronologically + + +5. **Create Parser Class** + + Developed a Parser class to process user commands and execute corresponding actions in the finance tracker + + +6. **Bug fixes** + + +7. **JUnit Test Cases** + +#### Contributions to User Guide + +- I authored the introduction section of the FinTrack user guide, providing an overview of the application's purpose and features. +- I authored the Quick Start section of the FinTrack user guide, which includes setup and installation instructions for new users. +- Explained and documented the following features: Login, `help`, `view-history`, `add-inflow`, `delete-inflow`, `edit-inflow`, `add-outflow`, `delete-outflow`, `edit-outflow`, `quit` +- Created a command summary for quick reference + +#### Contributions to Developer Guide + +- Explained the functionality, design considerations and implementation details for the `view-history` feature + +#### Contributions to Team-Based Tasks + +- Managed releases `v1.0`, `v2.0` on GitHub \ No newline at end of file diff --git a/file_paths.txt b/file_paths.txt new file mode 100644 index 0000000000..97c44f9f0e --- /dev/null +++ b/file_paths.txt @@ -0,0 +1,147 @@ +./compile.sh +./config/checkstyle/suppressions.xml +./config/checkstyle/checkstyle.xml +./docs/AboutUs.md +./docs/UserGuide.md +./docs/README.md +./docs/team/johndoe.md +./docs/DeveloperGuide.md +./text-ui-test/runtest.bat +./text-ui-test/EXPECTED-UNIX.TXT +./text-ui-test/EXPECTED.TXT +./text-ui-test/input.txt +./text-ui-test/runtest.sh +./text-ui-test/ACTUAL.TXT +./README.md +./gradle/wrapper/gradle-wrapper.jar +./gradle/wrapper/gradle-wrapper.properties +./file_paths.txt +./gradlew +./.gitignore +./CONTRIBUTORS.md +./.github/workflows/gradle.yml +./build.gradle +./.gradle/file-system.probe +./.gradle/vcs-1/gc.properties +./.gradle/7.6.2/executionHistory/executionHistory.lock +./.gradle/7.6.2/executionHistory/executionHistory.bin +./.gradle/7.6.2/gc.properties +./.gradle/7.6.2/fileChanges/last-build.bin +./.gradle/7.6.2/dependencies-accessors/gc.properties +./.gradle/7.6.2/dependencies-accessors/dependencies-accessors.lock +./.gradle/7.6.2/checksums/checksums.lock +./.gradle/7.6.2/fileHashes/fileHashes.lock +./.gradle/7.6.2/fileHashes/fileHashes.bin +./.gradle/7.6.2/fileHashes/resourceHashesCache.bin +./.gradle/buildOutputCleanup/cache.properties +./.gradle/buildOutputCleanup/outputFiles.bin +./.gradle/buildOutputCleanup/buildOutputCleanup.lock +./build/classes/java/main/financialtransactions/Outflow.class +./build/classes/java/main/financialtransactions/Inflow$Category.class +./build/classes/java/main/financialtransactions/Outflow$Category.class +./build/classes/java/main/financialtransactions/Inflow.class +./build/classes/java/main/financialtransactions/TransactionManager.class +./build/classes/java/main/financialtransactions/TransactionList.class +./build/classes/java/main/financialtransactions/Transaction.class +./build/classes/java/main/template/BaseDate.class +./build/classes/java/main/user/Authentication.class +./build/classes/java/main/user/BaseUser.class +./build/classes/java/main/parser/Parser.class +./build/classes/java/main/Main.class +./build/libs/duke.jar +./build/tmp/compileJava/previous-compilation-data.bin +./build/tmp/shadowJar/MANIFEST.MF +./.git/ORIG_HEAD +./.git/config +./.git/objects/61/1e6bb7d87d2cccc09b0b50d14139b41a791512 +./.git/objects/3b/0d120ecd000efe10980df7f669aa92a89cb6d1 +./.git/objects/3b/c6ecaf968345b1781f2c0ee5181c6f20753492 +./.git/objects/69/b73269c517a29a32c6bd89d1da23db57359782 +./.git/objects/58/0b21ab7b1b06454bf68220d25b6e827b8e4e50 +./.git/objects/93/21376bc46dd94f61072a09a4e4c15e1b3a3fc2 +./.git/objects/94/e47c854c2956015669f6479048070dfda6ded7 +./.git/objects/9c/653fd68c278c8ed9a2de5d31b5ef7922ce323d +./.git/objects/02/858711010960685a51c3d502e2f613fe3ddd22 +./.git/objects/b5/7c77e1bcb2c8dd6012df079b36b41c60f475d6 +./.git/objects/bb/03976d24fb3cab970cb7ba69cc1175aefb1909 +./.git/objects/be/5d40a35736ec6f87f508efcda93e984590bc83 +./.git/objects/a2/217952c96c66e8675be0eda9d44776384b7d1d +./.git/objects/ae/be20a1c00e6c2cb43d9a96f17b0a541e0ee0a3 +./.git/objects/f4/67d6c788a22ec10d6472f2ca0ea82c13f873c0 +./.git/objects/ee/ff41335d883f71be89bfb533287f9e2219254b +./.git/objects/fc/3473e18dc459060a128c90870b6a50e7a8e288 +./.git/objects/f2/2d6cc02ff313a68c49a2d156c0af55f19f677e +./.git/objects/f5/b1f11ac417dfb8bc861a5008ca615cf973e240 +./.git/objects/pack/pack-33a51df449e49363184d1bf26911c357f8f98bbb.pack +./.git/objects/pack/pack-33a51df449e49363184d1bf26911c357f8f98bbb.idx +./.git/objects/11/ec7ec31876b87bf9a0b385a02ee23c892e9089 +./.git/objects/7c/e087879386ccf1382f3c7f7350f88e1c0b69e2 +./.git/objects/16/0ade0a72af1206ef011178306a828531af1a9e +./.git/objects/73/aff39e301522547599fc3133100eeb694f2d03 +./.git/objects/73/aac78b03559379c1f7a36f5c85e68a9f04e6bf +./.git/objects/87/5f7ae5499ac9b3822ab035c12bd7a8f526f981 +./.git/objects/4c/d6663a05b86e59fa6bfe15cc55dbe2dc69c0f3 +./.git/objects/2a/80d4526441670c30fb13bef54d4ed5762adeaf +./.git/objects/2f/bac1978e0977851e58e5f356e47d125a79f729 +./.git/objects/36/fa3fd47322849dd042166312ffb4cc111ea7a0 +./.git/objects/96/22e98bee857326e6bdd17121dab1232a63d99b +./.git/objects/37/8b652464cb15c8af588a941e13e61960ea74f2 +./.git/objects/6c/070c08a384202511ca1907324ba05cde598c43 +./.git/objects/ba/2761a656cf9e6e5db75fc553fac528cad7e4a5 +./.git/objects/b9/d0bedbed673bbe42c22885cefe93f500271d35 +./.git/objects/ef/1fe424fa6fdf3d4d1f7b107b6beecf80f243f9 +./.git/objects/ef/66b5b45b63bd32daff9062fa89766374a31af8 +./.git/objects/e9/e0c259e0729b7151dd3e1f55b90e20ffc7ac47 +./.git/objects/e7/daae1ce0b64cb257ac1b2fe618f76a0ddc52a2 +./.git/objects/46/afb222e226e4dc0cdc594a15bd20ecf404f700 +./.git/objects/77/3e23a2ddcff842ce33aa3f6adfce4ec029e361 +./.git/objects/70/5a83bba6036ad80bf35eacf02c723b7a188228 +./.git/objects/70/770adf86b59d5dcdf91d6b822d6528fb310960 +./.git/objects/24/e9d88d5e4356278c4e8b7f86dceef09d840344 +./.git/objects/4f/e34084af9b4c5b1ebb95aa4d132c7d8e6588ec +./.git/objects/76/73f7ce5bf02cfd7f323fe67dd91eb05c10a1ea +./.git/objects/13/566b81b018ad684f3a35fee301741b2734c8f4 +./.git/HEAD +./.git/info/exclude +./.git/logs/HEAD +./.git/logs/refs/heads/pr/47 +./.git/logs/refs/heads/master +./.git/logs/refs/remotes/github-desktop-AY2324S2-CS2113-T15-3/HEAD +./.git/logs/refs/remotes/github-desktop-AY2324S2-CS2113-T15-3/master +./.git/logs/refs/remotes/origin/HEAD +./.git/logs/refs/remotes/origin/master +./.git/description +./.git/hooks/commit-msg.sample +./.git/hooks/pre-rebase.sample +./.git/hooks/pre-commit.sample +./.git/hooks/applypatch-msg.sample +./.git/hooks/fsmonitor-watchman.sample +./.git/hooks/pre-receive.sample +./.git/hooks/prepare-commit-msg.sample +./.git/hooks/post-update.sample +./.git/hooks/pre-merge-commit.sample +./.git/hooks/pre-applypatch.sample +./.git/hooks/pre-push.sample +./.git/hooks/update.sample +./.git/hooks/push-to-checkout.sample +./.git/refs/heads/pr/47 +./.git/refs/heads/master +./.git/refs/remotes/github-desktop-AY2324S2-CS2113-T15-3/HEAD +./.git/refs/remotes/github-desktop-AY2324S2-CS2113-T15-3/master +./.git/refs/remotes/origin/HEAD +./.git/refs/remotes/origin/master +./.git/index +./.git/packed-refs +./.git/COMMIT_EDITMSG +./.git/FETCH_HEAD +./gradlew.bat +./src/main/java/financialtransactions/TransactionManager.java +./src/main/java/financialtransactions/Inflow.java +./src/main/java/financialtransactions/TransactionList.java +./src/main/java/financialtransactions/Outflow.java +./src/main/java/financialtransactions/Transaction.java +./src/main/java/template/BaseDate.java +./src/main/java/user/Authentication.java +./src/main/java/user/BaseUser.java +./src/main/java/parser/Parser.java +./src/main/java/Main.java diff --git a/src/main/java/command/AddInflowCommand.java b/src/main/java/command/AddInflowCommand.java new file mode 100644 index 0000000000..5fa7444e75 --- /dev/null +++ b/src/main/java/command/AddInflowCommand.java @@ -0,0 +1,63 @@ +package command; + +import customexceptions.CategoryNotFoundException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.Inflow; +import financialtransactions.TransactionManager; + +public class AddInflowCommand extends BaseCommand { + String inflowName = null; + double inflowAmount = 0; + String inflowDate = null; + String inflowTime = null; + String inflowCategory = null; + + public AddInflowCommand(String[] commandParts) throws CategoryNotFoundException, IllegalArgumentException { + super(false, commandParts); + try { + createTransaction(); + } catch (IncorrectCommandSyntaxException e) { + System.out.println(e.getMessage()); + } + assert inflowCategory != null; + inflow.setCategory(inflowCategory); + } + + public void createTransaction() throws IncorrectCommandSyntaxException { + //@@author Kishen271828 + /* Iterates through the parts of the original command string that checks and updates + relevant inflow information. */ + for (int i = 1; i < commandParts.length; i++) { + String part = commandParts[i]; + if (part.startsWith("n/")) { + inflowName = part.substring(2); + } else if (part.startsWith("a/")) { + inflowAmount = Double.parseDouble(part.substring(2)); + if (inflowAmount <= 0) { + throw new IllegalArgumentException("Sorry, inflow amount must be positive."); + } + } else if (part.startsWith("d/")) { + inflowDate = part.substring(2); + } else if (part.startsWith("t/")) { + inflowTime = part.substring(2); + } else if (part.startsWith("c/")) { + inflowCategory = part.substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } + String inflowDateTime = inflowDate + " " + inflowTime; + inflow = new Inflow(inflowName, inflowAmount, inflowDateTime); + } + + public String execute(TransactionManager manager) { + if (!canExecute) { + return "Sorry, inflow not added."; + } + if (inflow.getDate().getDateTime() == null) { + return "Please enter a valid date or time"; + } + manager.addTransaction(inflow); + return "Ok. Added inflow"; + } +} diff --git a/src/main/java/command/AddOutflowCommand.java b/src/main/java/command/AddOutflowCommand.java new file mode 100644 index 0000000000..dd9191329e --- /dev/null +++ b/src/main/java/command/AddOutflowCommand.java @@ -0,0 +1,64 @@ +package command; + +import customexceptions.CategoryNotFoundException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.Outflow; +import financialtransactions.TransactionManager; + +public class AddOutflowCommand extends BaseCommand { + String outflowName = null; + double outflowAmount = 0.0; + String outflowDate = null; + String outflowTime = null; + String outflowCategory = null; + + public AddOutflowCommand(String[] commandParts) throws CategoryNotFoundException { + super(false, commandParts); + try { + createTransaction(); + } catch (IncorrectCommandSyntaxException | IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + assert outflowCategory != null; + outflow.setCategory(outflowCategory); + } + + public void createTransaction() throws IncorrectCommandSyntaxException { + //@@author Kishen271828 + /* Iterates through the parts of the original command string that checks and updates + relevant outflow information. */ + for (int i = 1 ; i < commandParts.length; i++) { + String part = commandParts[i]; + if (part.startsWith("n/")) { + outflowName = part.substring(2); + } else if (part.startsWith("a/")) { + outflowAmount = Double.parseDouble(part.substring(2)); + if (outflowAmount <= 0) { + throw new IllegalArgumentException("Sorry, inflow amount must be positive."); + } + } else if (part.startsWith("d/")) { + outflowDate = part.substring(2); + } else if (part.startsWith("t/")) { + outflowTime = part.substring(2); + } else if (part.startsWith("c/")) { + outflowCategory = part.substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } + String outflowDateTime = outflowDate + " " + outflowTime; + + outflow = new Outflow(outflowName, outflowAmount, outflowDateTime); + } + + public String execute(TransactionManager manager) { + if (!canExecute) { + return "Sorry, outflow not added."; + } + if (outflow.getDate().getDateTime() == null) { + return "Please enter a valid date or time"; + } + manager.addTransaction(outflow); + return "Ok. Added outflow"; + } +} diff --git a/src/main/java/command/AddReminderCommand.java b/src/main/java/command/AddReminderCommand.java new file mode 100644 index 0000000000..4dda79824f --- /dev/null +++ b/src/main/java/command/AddReminderCommand.java @@ -0,0 +1,62 @@ +package command; + +import customexceptions.CategoryNotFoundException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.Reminder; +import financialtransactions.TransactionManager; + +public class AddReminderCommand extends BaseCommand { + String reminderName = null; + double reminderAmount = 0.0; + String reminderDate = null; + String reminderTime = null; + String reminderCategory = null; + + public AddReminderCommand(String[] commandParts) throws CategoryNotFoundException { + super(false, commandParts); + try { + createTransaction(); + } catch (IncorrectCommandSyntaxException | IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + assert reminderCategory != null : "Reminder should have a valid category"; + reminder.setCategory(reminderCategory.toUpperCase()); + } + + public void createTransaction() throws IncorrectCommandSyntaxException { + /* Iterates through the parts of the original command string that checks and updates + relevant reminder information. */ + for (int i = 1; i < commandParts.length; i++) { + String part = commandParts[i]; + if (part.startsWith("n/")) { + reminderName = part.substring(2); + } else if (part.startsWith("a/")) { + reminderAmount = Double.parseDouble(part.substring(2)); + if (reminderAmount <= 0) { + throw new IllegalArgumentException("Sorry, inflow amount must be positive."); + } + } else if (part.startsWith("d/")) { + reminderDate = part.substring(2); + } else if (part.startsWith("t/")) { + reminderTime = part.substring(2); + } else if (part.startsWith("c/")) { + reminderCategory = part.substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } + String reminderDateTime = reminderDate + " " + reminderTime; + reminder = new Reminder(reminderName, reminderAmount, reminderDateTime); + } + + public String execute(TransactionManager manager) { + if (!canExecute) { + return "Sorry, reminder not added."; + } + if (reminder.getDate().getDateTime() == null) { + return "Please enter a valid date or time"; + } + manager.addTransaction(reminder); + return "Ok. Added reminder"; + } +} diff --git a/src/main/java/command/BaseCommand.java b/src/main/java/command/BaseCommand.java new file mode 100644 index 0000000000..5002beafe2 --- /dev/null +++ b/src/main/java/command/BaseCommand.java @@ -0,0 +1,56 @@ +package command; + +import financialtransactions.Inflow; +import financialtransactions.Outflow; +import financialtransactions.Reminder; +import financialtransactions.TransactionManager; + +//@@author dylansiew +public abstract class BaseCommand { + //@@author dylansiew + public boolean isExit; + protected Inflow inflow; + protected Outflow outflow; + protected Reminder reminder; + protected boolean canExecute = true; + protected TransactionManager manager; + String[] commandParts; + + public BaseCommand(boolean isExit, String[] commandParts) { + //@@author dylansiew + this.isExit = isExit; + this.commandParts = commandParts; + } + + public void setCanExecuteToFalse() { + this.canExecute = false; + } + + public abstract String execute(TransactionManager manager) throws Exception; + + public abstract void createTransaction() throws Exception; + + public boolean isExit() { + return this.isExit; + } + + public void setIsExit(boolean isExit) { + this.isExit = isExit; + } + + public Inflow getInflow() { + return inflow; + } + + public Outflow getOutflow() { + return outflow; + } + + public Reminder getReminder() { + return reminder; + } + + public void setManager(TransactionManager manager) { + this.manager = manager; + } +} diff --git a/src/main/java/command/DeleteInflowCommand.java b/src/main/java/command/DeleteInflowCommand.java new file mode 100644 index 0000000000..ec47c5a0bc --- /dev/null +++ b/src/main/java/command/DeleteInflowCommand.java @@ -0,0 +1,37 @@ +package command; + +import customexceptions.DeleteTransactionException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.TransactionManager; + +public class DeleteInflowCommand extends BaseCommand { + private int inflowIndex = -1; + public DeleteInflowCommand(String[] commandParts) { + super(false, commandParts); + } + + public void createTransaction() throws Exception { + //@@author Kishen271828 + if (commandParts[1].startsWith("i/")) { + inflowIndex = Integer.parseInt(commandParts[1].substring(2)); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + + assert inflowIndex != -1 : "inflowIndex should not be null"; + if (inflowIndex <= 0 || inflowIndex > manager.getNumOfInflows()) { + throw new DeleteTransactionException(); + } + try { + inflow = manager.getNthInflowFromList(inflowIndex); + } catch (Exception e) { + System.out.println("Sorry. " + e.getMessage()); + } + } + + public String execute(TransactionManager manager) throws Exception { + assert inflowIndex != -1 : "The inflowIndex should exist"; + manager.removeInflow(inflowIndex); + return "Ok. Inflow " + inflow.getName() + " | " + inflow.getCategory().toString() + " deleted"; + } +} diff --git a/src/main/java/command/DeleteOutflowCommand.java b/src/main/java/command/DeleteOutflowCommand.java new file mode 100644 index 0000000000..03a624c4cf --- /dev/null +++ b/src/main/java/command/DeleteOutflowCommand.java @@ -0,0 +1,39 @@ +package command; + +import customexceptions.DeleteTransactionException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.TransactionManager; + +public class DeleteOutflowCommand extends BaseCommand { + private int outflowIndex = -1; + + public DeleteOutflowCommand(String[] commandParts) { + super(false, commandParts); + } + + public void createTransaction() throws Exception { + //@@author Kishen271828 + if (commandParts[1].startsWith("i/")) { + outflowIndex = Integer.parseInt(commandParts[1].substring(2)); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + + assert outflowIndex != -1 : "outflowIndex should not be null"; + if (outflowIndex <= 0 || outflowIndex > manager.getNumOfOutflows()) { + throw new DeleteTransactionException(); + } + //@@author + try { + outflow = manager.getNthOutflowFromList(outflowIndex); + } catch (Exception e) { + System.out.println("Sorry. " + e.getMessage()); + } + } + + public String execute(TransactionManager manager) throws Exception { + assert outflowIndex != -1 : "The outflowIndex should exist"; + manager.removeOutflow(outflowIndex); + return "Ok. Outflow " + outflow.getName() + " " + outflow.getCategory().toString() + " deleted"; + } +} diff --git a/src/main/java/command/DeleteReminderCommand.java b/src/main/java/command/DeleteReminderCommand.java new file mode 100644 index 0000000000..2acdf7a009 --- /dev/null +++ b/src/main/java/command/DeleteReminderCommand.java @@ -0,0 +1,37 @@ +package command; + +import customexceptions.DeleteTransactionException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.TransactionManager; + +public class DeleteReminderCommand extends BaseCommand { + private int reminderIndex = -1; + + public DeleteReminderCommand(String[] commandParts) { + super(false, commandParts); + } + + public void createTransaction() throws Exception { + if (commandParts[1].startsWith("i/")) { + reminderIndex = Integer.parseInt(commandParts[1].substring(2)); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + + assert reminderIndex != -1 : "reminderIndex should exist"; + if (reminderIndex <= 0 || reminderIndex > manager.getNumOfReminders()) { + throw new DeleteTransactionException(); + } + try { + reminder = manager.getNthReminderFromList(reminderIndex); + } catch (Exception e) { + System.out.println("Sorry. " + e.getMessage()); + } + } + + public String execute(TransactionManager manager) throws Exception { + assert reminderIndex != -1 : "reminderIndex should exist"; + manager.removeReminder(reminderIndex); + return "Ok. Reminder " + reminder.getName() + " | " + reminder.getCategory().toString() + " deleted"; + } +} diff --git a/src/main/java/command/EditInflowCommand.java b/src/main/java/command/EditInflowCommand.java new file mode 100644 index 0000000000..1feab2a7a8 --- /dev/null +++ b/src/main/java/command/EditInflowCommand.java @@ -0,0 +1,74 @@ +package command; + +import customexceptions.EditTransactionException; +import customexceptions.CategoryNotFoundException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.Inflow; +import financialtransactions.TransactionManager; + +public class EditInflowCommand extends BaseCommand { + private int inflowIndex = -1; + private String inflowName = null; + private double inflowAmount = 0; + private String inflowDate = null; + private String inflowTime = null; + private String inflowCategory = null; + private Inflow updatedInflow; + + public EditInflowCommand(String[] commandParts) { + super(false, commandParts); + } + + @Override + public void createTransaction() throws Exception { + //@@author Kishen271828 + /* Iterates through the parts of the original command string that checks and updates + relevant inflow information. */ + for (int i = 1; i < commandParts.length; i++) { + String part = commandParts[i]; + if (part.startsWith("i/")) { + inflowIndex = Integer.parseInt(part.substring(2)); + if (inflowIndex <= 0 || inflowIndex > manager.getNumOfInflows()) { + throw new EditTransactionException(); + } + } else if (part.startsWith("n/")) { + inflowName = part.substring(2); + } else if (part.startsWith("a/")) { + inflowAmount = Double.parseDouble(part.substring(2)); + if (inflowAmount <= 0) { + throw new IllegalArgumentException("Sorry, inflow amount must be positive."); + } + } else if (part.startsWith("d/")) { + inflowDate = part.substring(2); + } else if (part.startsWith("t/")) { + inflowTime = part.substring(2); + } else if (part.startsWith("c/")) { + inflowCategory = part.substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } + assert inflowIndex != -1 : "inflow index should exist"; + assert inflowCategory != null : "inflow category should not be null"; + try { + inflow = manager.getNthInflowFromList(inflowIndex); + } catch (Exception e) { + System.out.println("Sorry, something went wrong: " + e.getMessage()); + } + + updatedInflow = new Inflow(inflowName, inflowAmount, inflowDate + " " + inflowTime); + try { + updatedInflow.setCategory(inflowCategory); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + } + + public String execute(TransactionManager manager) throws Exception { + if (!canExecute) { + return "Sorry, inflow not edited."; + } + manager.editInflow(inflowIndex, updatedInflow); + return "Ok. Edited inflow"; + } +} diff --git a/src/main/java/command/EditOutflowCommand.java b/src/main/java/command/EditOutflowCommand.java new file mode 100644 index 0000000000..cc0f165faa --- /dev/null +++ b/src/main/java/command/EditOutflowCommand.java @@ -0,0 +1,76 @@ +package command; + +import customexceptions.EditTransactionException; +import customexceptions.CategoryNotFoundException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.Outflow; +import financialtransactions.TransactionManager; + +public class EditOutflowCommand extends BaseCommand { + //@@author Kishen271828 + private int outflowIndex = -1; + private String outflowName = null; + private double outflowAmount = 0.0; + private String outflowDate = null; + private String outflowTime = null; + private String outflowCategory = null; + private Outflow updatedOutflow; + + public EditOutflowCommand(String[] commandParts) { + super(false, commandParts); + } + + @Override + public void createTransaction() throws Exception { + //@@author Kishen271828 + /* Iterates through the parts of the original command string that checks and updates + relevant outflow information. */ + for (int i = 1; i < commandParts.length; i++) { + String part = commandParts[i]; + if (part.startsWith("i/")) { + outflowIndex = Integer.parseInt(part.substring(2)); + if (outflowIndex <= 0 || outflowIndex > manager.getNumOfOutflows()) { + throw new EditTransactionException(); + } + } else if (part.startsWith("n/")) { + outflowName = part.substring(2); + } else if (part.startsWith("a/")) { + outflowAmount = Double.parseDouble(part.substring(2)); + if (outflowAmount <= 0) { + throw new IllegalArgumentException("Sorry, inflow amount must be positive."); + } + } else if (part.startsWith("d/")) { + outflowDate = part.substring(2); + } else if (part.startsWith("t/")) { + outflowTime = part.substring(2); + } else if (part.startsWith("c/")) { + outflowCategory = part.substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } + assert outflowIndex != -1 : "outflow index should exist."; + assert outflowCategory != null : "outflow category should not be null"; + try { + outflow = manager.getNthOutflowFromList(outflowIndex); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } catch (Exception e) { + System.out.println("Sorry. " + e.getMessage()); + } + updatedOutflow = new Outflow(outflowName, outflowAmount, outflowDate + " " + outflowTime); + try { + updatedOutflow.setCategory(outflowCategory); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + } + + public String execute(TransactionManager manager) throws Exception { + if (!canExecute) { + return "Sorry, outflow not edited."; + } + manager.editOutflow(outflowIndex, updatedOutflow); + return "Ok. Edited outflow"; + } +} diff --git a/src/main/java/command/EditReminderCommand.java b/src/main/java/command/EditReminderCommand.java new file mode 100644 index 0000000000..996f181d4d --- /dev/null +++ b/src/main/java/command/EditReminderCommand.java @@ -0,0 +1,74 @@ +package command; + +import customexceptions.EditTransactionException; +import customexceptions.CategoryNotFoundException; +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.Reminder; +import financialtransactions.TransactionManager; + +public class EditReminderCommand extends BaseCommand { + private int reminderIndex = -1; + private String reminderName = null; + private double reminderAmount = 0.0; + private String reminderDate = null; + private String reminderTime = null; + private String reminderCategory = null; + private Reminder updatedReminder; + + public EditReminderCommand(String[] commandParts) { + super(false, commandParts); + } + + @Override + public void createTransaction() throws Exception { + /* Iterates through the parts of the original command string that checks and updates + relevant reminder information. */ + for (int i = 1; i < commandParts.length; i++) { + String part = commandParts[i]; + if (part.startsWith("i/")) { + reminderIndex = Integer.parseInt(part.substring(2)); + if (reminderIndex <= 0 || reminderIndex > manager.getNumOfReminders()) { + throw new EditTransactionException(); + } + } else if (part.startsWith("n/")) { + reminderName = part.substring(2); + } else if (part.startsWith("a/")) { + reminderAmount = Double.parseDouble(part.substring(2)); + if (reminderAmount <= 0) { + throw new IllegalArgumentException("Sorry, inflow amount must be positive."); + } + } else if (part.startsWith("d/")) { + reminderDate = part.substring(2); + } else if (part.startsWith("t/")) { + reminderTime = part.substring(2); + } else if (part.startsWith("c/")) { + reminderCategory = part.substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } + assert reminderIndex != -1 : "outflow index should exist."; + assert reminderCategory != null : "outflow category should not be null"; + try { + reminder = manager.getNthReminderFromList(reminderIndex); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } catch (Exception e) { + System.out.println("Sorry. " + e.getMessage()); + } + updatedReminder = new Reminder(reminderName, reminderAmount, reminderDate + " " + reminderTime); + try { + updatedReminder.setCategory(reminderCategory); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + } + + public String execute(TransactionManager manager) throws Exception { + if (!canExecute) { + return "Sorry, reminder not edited."; + } + manager.editReminder(reminderIndex, updatedReminder); + return "Ok. Edited reminder"; + } +} diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java new file mode 100644 index 0000000000..16be7403e1 --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,18 @@ +package command; + +import financialtransactions.TransactionManager; + +public class ExitCommand extends BaseCommand { + + public ExitCommand(String[] commandParts) { + super(true,commandParts); + } + + public String execute(TransactionManager manager) { + return "Exiting application"; + } + + @Override + public void createTransaction() { + } +} diff --git a/src/main/java/command/GenerateReportCommand.java b/src/main/java/command/GenerateReportCommand.java new file mode 100644 index 0000000000..7ec6d674cb --- /dev/null +++ b/src/main/java/command/GenerateReportCommand.java @@ -0,0 +1,71 @@ +package command; + +import financialtransactions.TransactionManager; + +public class GenerateReportCommand extends BaseCommand { + + public GenerateReportCommand(String[] commandParts) { + super(false,commandParts); + } + + public String execute(TransactionManager manager) throws Exception{ + String monthString = ""; + int month = 0; + int year = 0; + if (commandParts[1].startsWith("m/")) { + monthString = commandParts[1].substring(2); + } + switch (monthString) { + case "JAN": + month = 1; + break; + case "FEB": + month = 2; + break; + case "MAR": + month = 3; + break; + case "APR": + month = 4; + break; + case "MAY": + month = 5; + break; + case "JUN": + month = 6; + break; + case "JUL": + month = 7; + break; + case "AUG": + month = 8; + break; + case "SEP": + month = 9; + break; + case "OCT": + month = 10; + break; + case "NOV": + month = 11; + break; + case "DEC": + month = 12; + break; + default: + return "Invalid month format. Month must be in the form of MMM"; + } + if (commandParts[2].startsWith("y/")) { + year = Integer.parseInt(commandParts[2].substring(2)); + if (year <= 0) { + return "Year must be a valid year"; + } + } + return manager.generateFullReport(monthString, month, year); + } + + @Override + public void createTransaction() { + } + +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..634a275b54 --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,33 @@ +package command; + +import financialtransactions.TransactionManager; + +public class HelpCommand extends BaseCommand { + public HelpCommand(String[] commandParts) { + super(false, commandParts); + } + + @Override + public String execute(TransactionManager manager) throws Exception { + String baseString = ""; + baseString += "Here are the available commands: \n"; + baseString += "1) add-inflow n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY\n"; + baseString += "2) add-outflow n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY\n"; + baseString += "3) add-reminder n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY\n"; + baseString += "4) delete-inflow i/INDEX\n"; + baseString += "5) delete-outflow i/INDEX\n"; + baseString += "6) delete-reminder i/INDEX\n"; + baseString += "7) edit-inflow i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY\n"; + baseString += "8) edit-outflow i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY\n"; + baseString += "9) edit-reminder i/INDEX n/NAME a/AMOUNT d/DATE t/TIME c/CATEGORY\n"; + baseString += "10) set-budget a/AMOUNT\n"; + baseString += "11) view-history n/NUM [w/CHART]\n"; + baseString += "12) generate-report m/MONTH y/YEAR\n"; + baseString += "13) quit \n"; + return baseString; + } + + @Override + public void createTransaction() { + } +} diff --git a/src/main/java/command/SetBudgetCommand.java b/src/main/java/command/SetBudgetCommand.java new file mode 100644 index 0000000000..f3b8d3f3b8 --- /dev/null +++ b/src/main/java/command/SetBudgetCommand.java @@ -0,0 +1,26 @@ +package command; + +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.TransactionManager; + +public class SetBudgetCommand extends BaseCommand{ + public SetBudgetCommand(String[] commandParts) { + super(false, commandParts); + } + + public String execute(TransactionManager manager) throws Exception{ + String budgetString = null; + if (commandParts[1].startsWith("a/")) { + budgetString = commandParts[1].substring(2); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + double budget = Double.parseDouble(budgetString); + manager.setBudget(budget); + return "Ok. Budget set."; + } + + @Override + public void createTransaction() { + } +} diff --git a/src/main/java/command/UndoCommand.java b/src/main/java/command/UndoCommand.java new file mode 100644 index 0000000000..c9bb4f0eb8 --- /dev/null +++ b/src/main/java/command/UndoCommand.java @@ -0,0 +1,104 @@ +package command; + +import customexceptions.UndoNotPermittedException; +import financialtransactions.Inflow; +import financialtransactions.Outflow; +import financialtransactions.Reminder; +import financialtransactions.TransactionManager; + +//@@author ChongXern +public class UndoCommand extends BaseCommand { + private static final int PERMITTED_UNDO_TIME = 10_000; + private int index; + private Inflow inflow; + private Outflow outflow; + private Reminder reminder; + private boolean canUndo = false; + private boolean canExecute; + private long startTime; + private String[] lastCommandParts; + + public UndoCommand(String[] commandParts) { + super(false, commandParts); + if (commandParts[0].contains("add") || commandParts[0].contains("delete")) { + lastCommandParts = commandParts; + } else { + lastCommandParts = null; + } + canExecute = false; + } + + public void setInflow(Inflow inflow) { + this.inflow = inflow; + this.outflow = null; + this.reminder = null; + } + public void setOutflow(Outflow outflow) { + this.inflow = null; + this.outflow = outflow; + this.reminder = null; + } + public void setReminder(Reminder reminder) { + this.inflow = null; + this.outflow = null; + this.reminder = reminder; + } + + public void setCanUndo(boolean canUndo, String[] lastCommandParts) { + this.canUndo = canUndo; + this.lastCommandParts = lastCommandParts; + startTime = System.currentTimeMillis(); + } + + private boolean didUndoTimerRunout() { + long timeDifference = System.currentTimeMillis() - startTime; + return timeDifference < PERMITTED_UNDO_TIME; + } + + public void allowExecute(String lastAction) { + canExecute = (lastAction != null); + } + + public String execute(TransactionManager manager) throws Exception { + if (!canExecute) { + throw new UndoNotPermittedException(true, true); + } + switch (lastCommandParts[0]) { // Compute how to undo the command to be undone + case "delete-inflow": + canUndo = true; + index = Integer.parseInt(lastCommandParts[1].substring(2)); + return manager.addTransaction(inflow); + case "delete-outflow": + canUndo = true; + index = Integer.parseInt(lastCommandParts[1].substring(2)); + return manager.addTransaction(outflow); + case "delete-reminder": + canUndo = true; + index = Integer.parseInt(lastCommandParts[1].substring(2)); + return manager.addTransaction(reminder); + case "add-inflow": + canUndo = true; + manager.removeTransaction(inflow); + break; + case "add-outflow": + canUndo = true; + manager.removeTransaction(outflow); + break; + case "add-reminder": + canUndo = true; + manager.removeTransaction(reminder); + break; + default: + throw new UndoNotPermittedException(didUndoTimerRunout(), true); + } + if (canUndo) { + canUndo = false; + return "Ok. " + lastCommandParts[0] + " has been undone."; + } + throw new UndoNotPermittedException(didUndoTimerRunout(), true); + } + + @Override + public void createTransaction() { + } +} diff --git a/src/main/java/command/ViewHistoryCommand.java b/src/main/java/command/ViewHistoryCommand.java new file mode 100644 index 0000000000..c0b9bc0fa0 --- /dev/null +++ b/src/main/java/command/ViewHistoryCommand.java @@ -0,0 +1,37 @@ +package command; + +import customexceptions.IncorrectCommandSyntaxException; +import financialtransactions.TransactionManager; + +public class ViewHistoryCommand extends BaseCommand { + + public ViewHistoryCommand(String[] commandParts) { + super(false,commandParts); + } + + public String execute(TransactionManager manager) throws Exception { + //@@author Kishen271828 + int numTransactions = 0; + if (commandParts[1].startsWith("n/")) { + String numTransactionsString = commandParts[1].substring(2); + try { + numTransactions = Integer.parseInt(numTransactionsString); + if (numTransactions <= 0) { + throw new IllegalArgumentException("Sorry, please input a positive index."); + } + } catch (NumberFormatException e) { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + } else if (commandParts[1].equals("all")) { + numTransactions = manager.getTransactionListSize(); + } else { + throw new IncorrectCommandSyntaxException(commandParts[0]); + } + boolean isIncludeBarChart = commandParts.length == 3 && commandParts[2].equalsIgnoreCase("w/chart"); + return manager.showLastNTransactions(numTransactions, isIncludeBarChart); + } + + @Override + public void createTransaction() { + } +} diff --git a/src/main/java/customexceptions/CategoryNotFoundException.java b/src/main/java/customexceptions/CategoryNotFoundException.java new file mode 100644 index 0000000000..4cb60e370b --- /dev/null +++ b/src/main/java/customexceptions/CategoryNotFoundException.java @@ -0,0 +1,9 @@ +package customexceptions; + +import java.util.Arrays; + +public class CategoryNotFoundException extends Exception { + public CategoryNotFoundException(Enum[] categories) { + super("Category not found. Available categories are:\n" + Arrays.toString(categories)); + } +} diff --git a/src/main/java/customexceptions/DeleteTransactionException.java b/src/main/java/customexceptions/DeleteTransactionException.java new file mode 100644 index 0000000000..a68ef38e2d --- /dev/null +++ b/src/main/java/customexceptions/DeleteTransactionException.java @@ -0,0 +1,7 @@ +package customexceptions; + +public class DeleteTransactionException extends Exception { + public DeleteTransactionException() { + super("Please enter a valid index. Use the view-history command to see which transaction you wish to delete"); + } +} diff --git a/src/main/java/customexceptions/EditTransactionException.java b/src/main/java/customexceptions/EditTransactionException.java new file mode 100644 index 0000000000..57abc270b0 --- /dev/null +++ b/src/main/java/customexceptions/EditTransactionException.java @@ -0,0 +1,7 @@ +package customexceptions; + +public class EditTransactionException extends Exception { + public EditTransactionException() { + super("Please enter a valid index. Use the view-history command to see which transaction you wish to edit"); + } +} diff --git a/src/main/java/customexceptions/ExceededAttemptsException.java b/src/main/java/customexceptions/ExceededAttemptsException.java new file mode 100644 index 0000000000..0339fbe848 --- /dev/null +++ b/src/main/java/customexceptions/ExceededAttemptsException.java @@ -0,0 +1,7 @@ +package customexceptions; + +public class ExceededAttemptsException extends Exception { + public ExceededAttemptsException() { + super("Attempts exceeded, exiting program"); + } +} diff --git a/src/main/java/customexceptions/ExitLoginException.java b/src/main/java/customexceptions/ExitLoginException.java new file mode 100644 index 0000000000..a8ae4b2054 --- /dev/null +++ b/src/main/java/customexceptions/ExitLoginException.java @@ -0,0 +1,7 @@ +package customexceptions; + +public class ExitLoginException extends Exception { + public ExitLoginException(){ + super("Exiting Login session"); + } +} diff --git a/src/main/java/customexceptions/InactivityTimeoutException.java b/src/main/java/customexceptions/InactivityTimeoutException.java new file mode 100644 index 0000000000..324502cf84 --- /dev/null +++ b/src/main/java/customexceptions/InactivityTimeoutException.java @@ -0,0 +1,19 @@ +package customexceptions; + +public class InactivityTimeoutException extends Exception { + private boolean isTimeOut = false; //Returns true if system is past 3 min, false otherwise + private boolean isGracePeriod = false; //Returns true if system is past 2.5 min, false otherwise + + public InactivityTimeoutException(boolean isTimeOut, boolean isGracePeriod) { + super("Session has timed out"); + this.isTimeOut = isTimeOut; + this.isGracePeriod = isGracePeriod; + } + + public boolean isTimeOut() { + return isTimeOut; + } + public boolean isGracePeriod() { + return isGracePeriod; + } +} diff --git a/src/main/java/customexceptions/IncompletePromptException.java b/src/main/java/customexceptions/IncompletePromptException.java new file mode 100644 index 0000000000..a039911fa7 --- /dev/null +++ b/src/main/java/customexceptions/IncompletePromptException.java @@ -0,0 +1,24 @@ +package customexceptions; + +public class IncompletePromptException extends Exception { + public static final String[] INSTRUCTIONS = { + "add-inflow", "add-outflow", "delete-inflow", "delete-outflow", "undo", "quit"}; + + public IncompletePromptException(String line) { + super(setMessage(line)); + } + + private static String setMessage(String prompt){ + String message = "Sorry, prompt input is unknown."; + for(String instr: IncompletePromptException.INSTRUCTIONS){ + if (instr.equals(prompt)){ + message = "Sorry, your prompt appears incomplete. Could you finish your sentence?"; + break; + } else if (instr.contains(prompt)){ + message = "Please prompt again with correct spelling. Did you mean to type " + instr; + break; + } + } + return message; + } +} diff --git a/src/main/java/customexceptions/IncorrectCommandSyntaxException.java b/src/main/java/customexceptions/IncorrectCommandSyntaxException.java new file mode 100644 index 0000000000..40a2c6215f --- /dev/null +++ b/src/main/java/customexceptions/IncorrectCommandSyntaxException.java @@ -0,0 +1,7 @@ +package customexceptions; + +public class IncorrectCommandSyntaxException extends Exception { + public IncorrectCommandSyntaxException(String command) { + super("Sorry, please use the correct syntax for " + command); + } +} diff --git a/src/main/java/customexceptions/UndoNotPermittedException.java b/src/main/java/customexceptions/UndoNotPermittedException.java new file mode 100644 index 0000000000..b932418730 --- /dev/null +++ b/src/main/java/customexceptions/UndoNotPermittedException.java @@ -0,0 +1,25 @@ +package customexceptions; + +public class UndoNotPermittedException extends Exception { + private static final String PAST_PERMITTED_TIME_MESSAGE = "Sorry, unable to undo. 10 seconds have passed"; + private static final String UNDOABLE_MESSAGE = "Sorry, the previous command cannot be undone."; + private static final String DEFAULT_MESSAGE = "Sorry, unable to undo previous action."; + private boolean didUndoTimerRunout; + private boolean isNotUndoable; + + public UndoNotPermittedException(boolean didUndoTimerRunout, boolean isNotUndoable) { + this.didUndoTimerRunout = didUndoTimerRunout; + this.isNotUndoable = isNotUndoable; + } + + @Override + public String getMessage() { + if (isNotUndoable) { + return UNDOABLE_MESSAGE; + } + if (didUndoTimerRunout) { + return PAST_PERMITTED_TIME_MESSAGE; + } + return DEFAULT_MESSAGE; + } +} diff --git a/src/main/java/customexceptions/UserNotFoundException.java b/src/main/java/customexceptions/UserNotFoundException.java new file mode 100644 index 0000000000..ba3a32aa8d --- /dev/null +++ b/src/main/java/customexceptions/UserNotFoundException.java @@ -0,0 +1,8 @@ +package customexceptions; +//@@author dylansiew +public class UserNotFoundException extends Exception { + //@@author dylansiew + public UserNotFoundException() { + super("Username not found. Exiting program"); + } +} diff --git a/src/main/java/financeproject/Main.java b/src/main/java/financeproject/Main.java new file mode 100644 index 0000000000..72f995c285 --- /dev/null +++ b/src/main/java/financeproject/Main.java @@ -0,0 +1,109 @@ +package financeproject; + +import command.BaseCommand; +import customexceptions.CategoryNotFoundException; +import customexceptions.ExceededAttemptsException; +import customexceptions.ExitLoginException; +import customexceptions.InactivityTimeoutException; +import customexceptions.IncompletePromptException; +import customexceptions.IncorrectCommandSyntaxException; +import customexceptions.UndoNotPermittedException; +import financialtransactions.TransactionManager; +import parser.Parser; +import storage.Storage; +import user.Authentication; +import user.BaseUser; +import user.InactivityTimer; +import userinterface.UI; + +public class Main { + public static void main(String[] args) throws SecurityException { + Storage storage = new Storage("./data"); // Storage manager for jar file + TransactionManager manager; + + UI ui = new UI(); + ui.printMessage("Welcome. Enter your username and password to login."); + + Parser parser = new Parser(ui); + BaseCommand baseCommand = null; + String response = ""; + + // Authenticating user + BaseUser user; + InactivityTimer inactivityTimer = new InactivityTimer(); + try { + ui.printMessage("Username: "); + response = ui.readInput(); + //user = storage.loadUser(response); + if (!response.toLowerCase().equals("bob")){ + ui.printMessage("User not found, exiting program"); + return; + } + user = storage.loadMockUser(); + Authentication.authenticateUser(user, ui); + ui.printMessage("User has been authenticated. Starting program..."); + } catch (ExceededAttemptsException | ExitLoginException e) { + ui.printMessage(e.getMessage()); + return; + } + try { + manager = storage.loadFile(user.getUsername()); + } catch (CategoryNotFoundException e){ + ui.printMessage(e.getMessage()); + return; + } + ui.printMessage(manager.generateQuickReport()); + + // Main program flow + ui.printLine(); + ui.printMessage("How can we help you financially today?\n" + // + "Type 'help' to view guide"); + ui.printLine(); + do { + response = ui.readInput(); + parser.setManager(manager); + try { + baseCommand = parser.parseCommand(response); + response = baseCommand.execute(manager); + ui.printLine(); + ui.printMessage(response); + ui.printLine(); + inactivityTimer.resetTimer(); + } catch (IncompletePromptException | IncorrectCommandSyntaxException | + IllegalArgumentException | UndoNotPermittedException e) { + ui.printMessage(e.getMessage()); + } catch (Exception e) { + ui.printMessage("Uh-oh, something went wrong: " + e.getMessage()); + } + + try{ + ui.printMessage(storage.saveFile(user.getUsername(), manager)); + } catch (Exception e){ + ui.printMessage(e.getMessage()); + } + + try { + inactivityTimer.checkTimeElapsed(); + } catch (InactivityTimeoutException e) { + if (e.isTimeOut()) { + assert baseCommand != null; + baseCommand.setIsExit(true); + } else if (e.isGracePeriod()) { + ui.printMessage("Some time has passed. Would you still like to continue: "); + String wantToContinue = ui.readInput(); + if (wantToContinue.equalsIgnoreCase("y") || + wantToContinue.equalsIgnoreCase("yes")) { + ui.printMessage("Session continued."); + inactivityTimer.resetTimer(); + } else if (wantToContinue.equalsIgnoreCase("n") || + wantToContinue.equalsIgnoreCase("no")) { + ui.printMessage("Session ended. "); + assert baseCommand != null; + baseCommand.setIsExit(true); + } + } + } + } while (baseCommand == null || !baseCommand.isExit()); + ui.closeScanner(); + } +} diff --git a/src/main/java/financeproject/data/Bob.txt b/src/main/java/financeproject/data/Bob.txt new file mode 100644 index 0000000000..87b710f79b --- /dev/null +++ b/src/main/java/financeproject/data/Bob.txt @@ -0,0 +1,9 @@ +100.00 +Payday|800.00|Feb 22 2022 12:09AM|INCOME|I +Payday|800.00|Feb 22 2022 12:09AM|INCOME|I +Salary|400.00|May 23 2022 07:00PM|INCOME|I +Salary|400.00|May 23 2022 07:00PM|INCOME|I +Salary|400.00|May 23 2022 07:00PM|INCOME|I +Refund|100.00|Jun 23 2023 05:00PM|REFUND|I +Rent|-1500.00|Jun 23 2023 06:00PM|RENT|O +Rent|-1500.00|Jun 23 2023 06:00PM|RENT|O diff --git a/src/main/java/financeproject/data/passwords.txt b/src/main/java/financeproject/data/passwords.txt new file mode 100644 index 0000000000..962ea2afd5 --- /dev/null +++ b/src/main/java/financeproject/data/passwords.txt @@ -0,0 +1 @@ +Bob|password \ No newline at end of file diff --git a/src/main/java/financialtransactions/Inflow.java b/src/main/java/financialtransactions/Inflow.java new file mode 100644 index 0000000000..8d97a07af3 --- /dev/null +++ b/src/main/java/financialtransactions/Inflow.java @@ -0,0 +1,36 @@ +package financialtransactions; + +import customexceptions.CategoryNotFoundException; + +public class Inflow extends Transaction { + public enum Category { + INCOME, INVESTMENT, GIFT, LOAN, REFUND, OTHER + } + + public Inflow(String name, double amount, String date) { + super(name, amount, date); + } + + @Override + public void setCategory(String category) throws CategoryNotFoundException { + if (!isValidCategory(category)) { + throw new CategoryNotFoundException(Category.values()); + } + super.category = Category.valueOf(category.toUpperCase()); + } + + public boolean isValidCategory(String category) { + for (Category enumCategory : Category.values()) { + if (enumCategory.toString().equalsIgnoreCase(category)) { + return true; + } + } + return false; + } + + + @Override + public String toSave() { + return super.toSave() + "|I\n"; + } +} diff --git a/src/main/java/financialtransactions/Outflow.java b/src/main/java/financialtransactions/Outflow.java new file mode 100644 index 0000000000..df678075d6 --- /dev/null +++ b/src/main/java/financialtransactions/Outflow.java @@ -0,0 +1,33 @@ +package financialtransactions; + +import customexceptions.CategoryNotFoundException; + +public class Outflow extends Transaction { + public enum Category { + FOOD, RENT, DEBT, SHOPPING, TREAT, EDUCATION, TAX, OTHER + } + + public Outflow(String name, double amount, String date) { + super(name, -1.00 * amount, date); + } + + public void setCategory(String category) throws CategoryNotFoundException { + if (!isValidCategory(category)) { + throw new CategoryNotFoundException(Category.values()); + } + this.category = Category.valueOf(category.toUpperCase()); + } + public boolean isValidCategory(String category) { + for (Category enumCategory : Category.values()) { + if (enumCategory.toString().equalsIgnoreCase(category)) { + return true; + } + } + return false; + } + + @Override + public String toSave() { + return super.toSave() + "|O\n"; + } +} diff --git a/src/main/java/financialtransactions/Reminder.java b/src/main/java/financialtransactions/Reminder.java new file mode 100644 index 0000000000..f4302e24e8 --- /dev/null +++ b/src/main/java/financialtransactions/Reminder.java @@ -0,0 +1,34 @@ +package financialtransactions; + +import customexceptions.CategoryNotFoundException; + +public class Reminder extends Transaction { + public enum Category { + INSTALLMENT, CREDITCARD, UTILITIES, OTHER + } + + public Reminder(String name, double amount, String date) { + super(name, -1.00 * amount, date); + } + + public void setCategory(String category) throws CategoryNotFoundException { + if (!isValidCategory(category)) { + throw new CategoryNotFoundException(Category.values()); + } + this.category = Category.valueOf(category.toUpperCase()); + } + + public boolean isValidCategory(String category) { + for (Category enumCategory : Category.values()) { + if (enumCategory.toString().equalsIgnoreCase(category)) { + return true; + } + } + return false; + } + + @Override + public String toSave() { + return super.toSave() + "|R\n"; + } +} diff --git a/src/main/java/financialtransactions/Transaction.java b/src/main/java/financialtransactions/Transaction.java new file mode 100644 index 0000000000..f53fc6d0d3 --- /dev/null +++ b/src/main/java/financialtransactions/Transaction.java @@ -0,0 +1,59 @@ +package financialtransactions; + +import customexceptions.CategoryNotFoundException; +import template.BaseDate; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public abstract class Transaction { + private static final int NAME_MAX_LEN = 30; + protected String name; + protected double amount; + protected BaseDate date; + protected T category; + + public Transaction(String name, double amount, String date) { + assert name != null : "NULL name detected"; + this.name = name; + this.amount = amount; + if (date == null){ + this.date = new BaseDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); + } else{ + this.date = new BaseDate(date); + } + } + + public String getName() { + return name; + } + + protected double getAmount() { + return amount; + } + + public T getCategory() { + return category; + } + + protected abstract void setCategory(String category) throws CategoryNotFoundException; + protected abstract boolean isValidCategory(String category); + + @Override + public String toString() { + return String.format("Name: %s, Amount: %.2f, Date: %s", name, amount, date.toString()); + } + + public String toSave() { + return String.format("%s|%.2f|%s|%s", name, amount, date.toString(), category); + } + + public BaseDate getDate() { + return date; + } + + public int compareTo(Transaction otherTransaction) { + return this.date.compareTo(otherTransaction.getDate()); + } + +} diff --git a/src/main/java/financialtransactions/TransactionComparator.java b/src/main/java/financialtransactions/TransactionComparator.java new file mode 100644 index 0000000000..f80473ce08 --- /dev/null +++ b/src/main/java/financialtransactions/TransactionComparator.java @@ -0,0 +1,12 @@ +//@@author Kishen271828 +package financialtransactions; + +import java.util.Comparator; + +public class TransactionComparator implements Comparator> { + @Override + public int compare(Transaction t1, Transaction t2) { + // Compare the dates of the transactions + return t1.getDate().compareTo(t2.getDate()); + } +} diff --git a/src/main/java/financialtransactions/TransactionList.java b/src/main/java/financialtransactions/TransactionList.java new file mode 100644 index 0000000000..c54bbb65ed --- /dev/null +++ b/src/main/java/financialtransactions/TransactionList.java @@ -0,0 +1,200 @@ +package financialtransactions; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; + +public class TransactionList> { + private ArrayList transactionList; + private String transactionsType; + + public TransactionList(){ + this.transactionList = new ArrayList<>(); + } + + public int getTransactionListSize() { + return this.transactionList.size(); + } + + public Transaction getNthTransaction(int n) throws Exception { + if (n >= this.transactionList.size()) { + throw new Exception("Invalid index"); + } + return this.transactionList.get(n); + } + + public int getIndexOfParticularTransaction(T particularTransaction) { + return this.transactionList.indexOf(particularTransaction); + } + + public boolean addTransaction(T newTransaction) { + if (newTransaction != null) { + transactionList.add(newTransaction); + return true; + } + return false; + } + + public boolean removeTransactionIndex (int index) { + transactionList.remove(index); + return true; + } + + public boolean editTransactionIndex (int index, T transaction) { + transactionList.set(index, transaction); + return true; + } + + public double getBalance(){ + double balance = 0.00; + for(Transaction transaction : transactionList){ + balance += transaction.getAmount(); + } + return balance; + } + + @Override + public String toString(){ + StringBuilder baseString = new StringBuilder("Transactions: \n"); + int index = 1; + for(T transaction : transactionList){ + baseString.append(String.format("%d) %s\n", index, transaction.toString())); + index += 1; + } + return baseString.toString(); + } + + //@@author chenhowy + + /** + * Returns a string of all the past transactions. + * Going through each transaction, the method calls toSave for each of them. + * This generates one line for each transaction that will be appended to baseString. + * + * @return A string of all past transactions. + */ + public String toSave() { + StringBuilder baseString = new StringBuilder(); + for (T transaction : transactionList) { + baseString.append(transaction.toSave()); + } + return baseString.toString(); + } + //@@author + + protected String printTransactionsSafeInfo() { + int index = 1; + String response = ""; + for (T transaction : transactionList) { + response += String.format("%d\t%s | %s | %s", index++, transaction.getName(), + transaction.getCategory(), transaction.getClass()); + } + return response; + } + + public String getTransactionsType() { + return transactionsType; + } + + public void setTransactionsType(String transactionsType) { + if (transactionsType.equals("Mixed")) { + return; + } + if (this.transactionsType == null) { + this.transactionsType = transactionsType; + } else if (!this.transactionsType.equals(transactionsType)) { + this.transactionsType = "Mixed"; + } + } + + //@@author Kishen271828 + public void sortTransactions() { + transactionList.sort(new TransactionComparator()); + } + //@@author + + public class NameComparator> implements Comparator { + @Override + public int compare(T o1, T o2) { + return o2.getName().compareToIgnoreCase(o1.getName()); + } + } + + public class DateComparator> implements Comparator { + @Override + public int compare(T o1, T o2) { + return o2.getDate().getDateTime().compareTo(o1.getDate().getDateTime()); + } + } + + public class FlowComparator> implements Comparator { + @Override + public int compare(T o1, T o2) { + return o2.getClass().toString().compareTo(o1.getClass().toString()); + } + } + + public void sortList() { + this.transactionList.sort(new FlowComparator<>()); + this.transactionList.sort(new NameComparator<>()); + } + + public void sortListByDate() { + this.transactionList.sort(new DateComparator<>()); + } + + //@@author chenhowy + + /** + * Returns the amount of money spent in the current month. + * The method checks if the transaction happened from the start of the current calendar month till today. + * If it falls in the time period, it will be added to the total amount. + * + * @return A double of the amount of money spent in the current month. + */ + public double totalSpentInCurrentMonth() { + double amount = 0; + for (T transaction : transactionList) { + if ((transaction.getDate().getDateTime().getMonth() == LocalDateTime.now().getMonth()) && + transaction.getDate().getDateTime().isBefore(LocalDateTime.now())) { + amount += transaction.getAmount(); + } + } + return amount; + } + + /** + * Returns the amount of money spent in a certain month of a certain year. + * This method can also sum up the total amount of money earned in that month. + * + * @param month The month which the user wants to query. + * @param year The year which the user wants to query. + * @return A double of the amount of money spent in the certain month. + */ + public double getTotalSpentInMonth(int month, int year) { + double amount = 0; + for (T transaction : transactionList) { + if (transaction.getDate().getDateTime().getMonthValue() == month && + transaction.getDate().getDateTime().getYear() == year) { + amount += transaction.getAmount(); + } + } + return amount; + } + + /** + * Returns the number of reminders that have yet to happen. + * + * @return An integer of the number of reminders that have not occurred. + */ + public int getRemindersAfterToday() { + int numberOfTransactions = 0; + LocalDateTime today = LocalDateTime.now(); + for (T transaction : transactionList) { + if (transaction.getDate().getDateTime().isAfter(today)) { + numberOfTransactions++; + } + } + return numberOfTransactions; + } +} diff --git a/src/main/java/financialtransactions/TransactionManager.java b/src/main/java/financialtransactions/TransactionManager.java new file mode 100644 index 0000000000..8836add6bf --- /dev/null +++ b/src/main/java/financialtransactions/TransactionManager.java @@ -0,0 +1,286 @@ +package financialtransactions; + + +import storage.BarChart; + +import java.time.LocalDateTime; + +public class TransactionManager { + private TransactionList> transactionList; + private TransactionList inflows; + private TransactionList outflows; + private TransactionList reminders; + + private double budget = 0.00; + + public TransactionManager() { + this.transactionList = new TransactionList<>(); + this.inflows = new TransactionList<>(); + this.outflows = new TransactionList<>(); + this.reminders = new TransactionList<>(); + } + + public void setBudget(double budget) { + this.budget = budget; + } + + public String addTransaction(Transaction transaction) { + transactionList.addTransaction(transaction); + transactionList.sortTransactions(); + if (transaction instanceof Inflow) { + Inflow inflow = (Inflow) transaction; + transactionList.setTransactionsType("Inflow"); + inflows.addTransaction(inflow); + return String.format("Added: %s", inflow); + } else if (transaction instanceof Outflow) { + Outflow outflow = (Outflow) transaction; + transactionList.setTransactionsType("Outflow"); + outflows.addTransaction(outflow); + return String.format("Added: %s", outflow); + } else if (transaction instanceof Reminder) { + Reminder reminder = (Reminder) transaction; + transactionList.setTransactionsType("Reminder"); + reminders.addTransaction(reminder); + return String.format("Added: %s", reminder); + } + return "Invalid transaction type."; + } + + public String removeTransaction(Transaction transaction) { + int index = transactionList.getIndexOfParticularTransaction(transaction); + transactionList.removeTransactionIndex(index); + if (transaction instanceof Inflow) { + Inflow inflow = (Inflow) transaction; + int inflowIndex = inflows.getIndexOfParticularTransaction(inflow); + inflows.removeTransactionIndex(inflowIndex); + return String.format("Removed: %s", inflow.toString()); + } else if (transaction instanceof Outflow) { + Outflow outflow = (Outflow) transaction; + int outflowIndex = outflows.getIndexOfParticularTransaction(outflow); + outflows.removeTransactionIndex(outflowIndex); + return String.format("Removed: %s", outflow.toString()); + } else if (transaction instanceof Reminder) { + Reminder reminder = (Reminder) transaction; + int reminderIndex = reminders.getIndexOfParticularTransaction(reminder); + reminders.removeTransactionIndex(reminderIndex); + return String.format("Removed: %s", reminder.toString()); + } + return "Invalid transaction type."; + } + + public void removeInflow(int index) throws Exception { + int numOfInflows = inflows.getTransactionListSize(); + Inflow transactionRemoved = (Inflow) inflows.getNthTransaction(numOfInflows - index); + transactionList.removeTransactionIndex(transactionList.getIndexOfParticularTransaction(transactionRemoved)); + transactionList.sortTransactions(); + + inflows.removeTransactionIndex(numOfInflows - index); + inflows.sortTransactions(); + } + + public void removeOutflow(int index) throws Exception { + int numOfOutflows = outflows.getTransactionListSize(); + Outflow transactionRemoved = (Outflow) outflows.getNthTransaction(numOfOutflows - index); + transactionList.removeTransactionIndex(transactionList.getIndexOfParticularTransaction(transactionRemoved)); + transactionList.sortTransactions(); + + outflows.removeTransactionIndex(numOfOutflows - index); + outflows.sortTransactions(); + } + + public void removeReminder(int index) throws Exception { + int numOfReminders = reminders.getTransactionListSize(); + Reminder transactionRemoved = getNthReminderFromList(index); + transactionList.removeTransactionIndex(transactionList.getIndexOfParticularTransaction(transactionRemoved)); + transactionList.sortTransactions(); + + reminders.removeTransactionIndex(reminders.getIndexOfParticularTransaction(transactionRemoved)); + reminders.sortTransactions(); + } + + public Inflow editInflow(int index, Transaction updatedTransaction) throws Exception { + int numOfInflows = inflows.getTransactionListSize(); + Inflow transactionEdited = (Inflow) inflows.getNthTransaction(numOfInflows - index); + transactionList.editTransactionIndex(transactionList.getIndexOfParticularTransaction(transactionEdited), + updatedTransaction); + transactionList.sortTransactions(); + + inflows.editTransactionIndex(numOfInflows - index, (Inflow) updatedTransaction); + inflows.sortTransactions(); + return transactionEdited; + } + + public Outflow editOutflow(int index, Transaction updatedTransaction) throws Exception { + int numOfOutflows = outflows.getTransactionListSize(); + Transaction transactionEdited = outflows.getNthTransaction(numOfOutflows - index); + transactionList.editTransactionIndex(transactionList.getIndexOfParticularTransaction(transactionEdited), + updatedTransaction); + transactionList.sortTransactions(); + + outflows.editTransactionIndex(numOfOutflows - index, (Outflow) updatedTransaction); + outflows.sortTransactions(); + return (Outflow) transactionEdited; + } + + public boolean editReminder(int index, Transaction updatedTransaction) throws Exception { + int numOfReminders = reminders.getTransactionListSize(); + Transaction transactionEdited = reminders.getNthTransaction(numOfReminders - index); + transactionList.editTransactionIndex(transactionList.getIndexOfParticularTransaction(transactionEdited), + updatedTransaction); + return reminders.editTransactionIndex(numOfReminders - index, (Reminder) updatedTransaction); + } + + public double getTotalBalance() { + double inflowBalance = inflows.getBalance(); + double outflowBalance = outflows.getBalance(); + return inflowBalance + outflowBalance; + } + + @Override + public String toString() { + return "Inflows:\n" + inflows.toString() + "\nOutflows:\n" + outflows.toString(); + } + + public String showLastNTransactions(int n, boolean isIncludeBarChart) throws Exception { + int listSize = transactionList.getTransactionListSize(); + if (n > listSize) { + throw new Exception("Invalid index"); + } + int index = 1; + String returnedText = "Inflows:\nTransactions:\n"; + for (int i = listSize - 1; i > listSize - n - 1; i--) { + Transaction transaction = transactionList.getNthTransaction(i); + if (transaction instanceof Inflow) { + returnedText += String.format("%d) %s\n", index, transactionList.getNthTransaction(i).toString()); + index++; + } + } + + index = 1; + returnedText += "\nOutflows:\nTransactions:\n"; + for (int i = listSize - 1; i > listSize - n - 1; i--) { + Transaction transaction = transactionList.getNthTransaction(i); + if (transaction instanceof Outflow) { + returnedText += String.format("%d) %s\n", index, transactionList.getNthTransaction(i).toString()); + index++; + } + } + + index = 1; + returnedText += "\nReminders:\nTransactions:\n"; + for (int i = listSize - 1; i > listSize - n - 1; i--) { + Transaction transaction = transactionList.getNthTransaction(i); + if (transaction instanceof Reminder) { + returnedText += String.format("%d) %s\n", index, transactionList.getNthTransaction(i).toString()); + index++; + } + } + + if (isIncludeBarChart) { + BarChart> barChart = new BarChart<>(transactionList); + returnedText = barChart.generateBarChart() + returnedText; + } + + return returnedText; + } + + public String toSave() { + return String.format("%.2f\n", budget) + transactionList.toSave(); + } + + //@@author chenhowy + /** + * Returns a string that contains some basic information. + * This string is to be displayed on launch of the app. + * It contains amount spent in the current month, budget left and reminders. + * + * @return A string of basic information to show to the user on launch. + */ + public String generateQuickReport() { + String baseString = ""; + baseString += String.format("You have spent " + + "%.2f in the current month.\n", outflows.totalSpentInCurrentMonth()); + baseString += String.format("With a budget of " + + "%.2f, you have %.2f left to spend.\n", budget, budget - outflows.totalSpentInCurrentMonth() - + reminders.totalSpentInCurrentMonth()); + baseString += String.format("You have " + + "%d upcoming payments that require your attention", reminders.getRemindersAfterToday()); + return baseString; + } + + /** + * Returns a string that contains information about a certain month that has passed. + * It first checks if the month has already passed. + * It will tell the user their income, spending and savings from that month. + * + * @param monthString A string of the certain month. + * @param month An integer value of the certain month. + * @param year An integer value of the certain year. + * @return A string that contains information about the month. + */ + public String generateFullReport(String monthString, int month, int year) { + if (!isBefore(month, year)) { + return "Please enter a month that is before the current month"; + } + String baseString = ""; + baseString += String.format("In the month of %s %s, " + + "you had an income of $%.2f.\n", monthString, year, inflows.getTotalSpentInMonth(month, year)); + baseString += String.format("You spent $%.2f.\n", outflows.getTotalSpentInMonth(month, year) + + reminders.getTotalSpentInMonth(month, year)); + baseString += String.format("You managed to save $%.2f!", + inflows.getTotalSpentInMonth(month, year) - outflows.getTotalSpentInMonth(month, year) - + reminders.getTotalSpentInMonth(month, year)); + return baseString; + } + + /** + * Returns true if the month is before the current month. + * If the year is the same as the current year, the month must be before the current month. + * If the year is before the current year, the month does not matter. + * + * @param month Integer value of the month in question. + * @param year Integer value of the year in question. + * @return True if the month is before the current month. + */ + public boolean isBefore(int month, int year) { + LocalDateTime today = LocalDateTime.now(); + int todayMonth = today.getMonthValue(); + int todayYear = today.getYear(); + if (year < todayYear) { + return true; + } + return year == todayYear && month < todayMonth; + } + + public int getTransactionListSize() { + return transactionList.getTransactionListSize(); + } + + public int getNumOfInflows() { + return inflows.getTransactionListSize(); + } + + public int getNumOfOutflows() { + return outflows.getTransactionListSize(); + } + + public int getNumOfReminders() { + return reminders.getTransactionListSize(); + } + + public int findTransactionIndex(Transaction transaction) { + return transactionList.getIndexOfParticularTransaction(transaction); + } + + public Inflow getNthInflowFromList(int n) throws Exception { + return (Inflow) inflows.getNthTransaction(getNumOfInflows() - n); + } + + public Outflow getNthOutflowFromList(int n) throws Exception { + return (Outflow) outflows.getNthTransaction(getNumOfOutflows() - n); + } + + public Reminder getNthReminderFromList(int n) throws Exception { + return (Reminder) reminders.getNthTransaction(getNumOfReminders() - n); + } +} diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java new file mode 100644 index 0000000000..ab847fb2f6 --- /dev/null +++ b/src/main/java/parser/Parser.java @@ -0,0 +1,177 @@ +package parser; + +import command.AddInflowCommand; +import command.AddOutflowCommand; +import command.AddReminderCommand; +import command.DeleteInflowCommand; +import command.DeleteOutflowCommand; +import command.DeleteReminderCommand; +import command.SetBudgetCommand; +import command.EditInflowCommand; +import command.EditOutflowCommand; +import command.EditReminderCommand; +import command.UndoCommand; +import command.ExitCommand; +import command.ViewHistoryCommand; +import command.BaseCommand; +import command.HelpCommand; +import command.GenerateReportCommand; +import customexceptions.IncompletePromptException; +import userinterface.UI; + +import financialtransactions.TransactionManager; + +public class Parser { + UI ui; + + // For undo functionality + String[] lastCommandParts; + BaseCommand lastCommand; + UndoCommand undoCommand = new UndoCommand(new String[]{" "}); + String lastAction; + TransactionManager manager; // Fetches transactions based on indexes only. + public Parser(UI ui) { + this.ui = ui; + } + + public void setManager(TransactionManager manager) { + this.manager = manager; + undoCommand.setManager(manager); + } + + public BaseCommand parseCommand(String command) throws Exception { + String[] commandParts = command.split("\\s+"); + String action = commandParts[0]; + switch (action) { + case "help": + undoCommand.setCanUndo(false, null); + lastAction = null; + return new HelpCommand(commandParts); + case "add-inflow": + if (commandParts.length < 6) { + throw new IncompletePromptException(command); + } + lastCommand = new AddInflowCommand(commandParts); + lastAction = action; + lastCommandParts = commandParts; + undoCommand.setCanUndo(true, commandParts); + undoCommand.setInflow(lastCommand.getInflow()); + return lastCommand; + case "add-outflow": + if (commandParts.length < 6) { + throw new IncompletePromptException(command); + } + lastCommand = new AddOutflowCommand(commandParts); + lastAction = action; + lastCommandParts = commandParts; + undoCommand.setCanUndo(true, commandParts); + undoCommand.setOutflow(lastCommand.getOutflow()); + return lastCommand; + case "add-reminder": + if (commandParts.length < 6) { + throw new IncompletePromptException(command); + } + lastCommand = new AddReminderCommand(commandParts); + lastAction = action; + lastCommandParts = commandParts; + undoCommand.setCanUndo(true, commandParts); + undoCommand.setReminder(lastCommand.getReminder()); + return lastCommand; + case "delete-inflow": + if (commandParts.length < 2) { + throw new IncompletePromptException(command); + } + lastAction = action; + lastCommandParts = commandParts; + undoCommand.setCanUndo(true, commandParts); + + lastCommand = new DeleteInflowCommand(commandParts); + lastCommand.setManager(manager); + lastCommand.createTransaction(); + undoCommand.setInflow(lastCommand.getInflow()); + return lastCommand; + case "delete-outflow": + if (commandParts.length < 2) { + throw new IncompletePromptException(command); + } + lastCommand = new DeleteOutflowCommand(commandParts); + lastCommand.setManager(manager); + lastCommand.createTransaction(); + lastAction = action; + lastCommandParts = commandParts; + undoCommand.setCanUndo(true, commandParts); + undoCommand.setOutflow(lastCommand.getOutflow()); + return lastCommand; + case "delete-reminder": + if (commandParts.length < 2) { + throw new IncompletePromptException(command); + } + lastCommand = new DeleteReminderCommand(commandParts); + lastCommand.setManager(manager); + lastCommand.createTransaction(); + lastAction = action; + lastCommandParts = commandParts; + undoCommand.setCanUndo(true, commandParts); + undoCommand.setReminder(lastCommand.getReminder()); + return lastCommand; + case "edit-inflow": + if (commandParts.length < 7) { + throw new IncompletePromptException(command); + } + lastCommand = new EditInflowCommand(commandParts); + lastCommand.setManager(manager); + lastCommand.createTransaction(); + lastAction = null; + undoCommand.setCanUndo(false, null); + return lastCommand; + case "edit-outflow": + if (commandParts.length < 7) { + throw new IncompletePromptException(command); + } + lastCommand = new EditOutflowCommand(commandParts); + lastCommand.setManager(manager); + lastCommand.createTransaction(); + lastAction = null; + undoCommand.setCanUndo(false, null); + return lastCommand; + case "edit-reminder": + if (commandParts.length < 7) { + throw new IncompletePromptException(command); + } + lastCommand = new EditReminderCommand(commandParts); + lastCommand.setManager(manager); + lastCommand.createTransaction(); + lastAction = null; + undoCommand.setCanUndo(false, null); + return lastCommand; + case "set-budget": + if (commandParts.length < 2) { + throw new IncompletePromptException(command); + } + lastAction = null; + undoCommand.setCanUndo(false, null); + return new SetBudgetCommand(commandParts); + case "view-history": + if (commandParts.length < 2) { + throw new IncompletePromptException(command); + } + lastAction = null; + return new ViewHistoryCommand(commandParts); + case "generate-report": + if (commandParts.length < 3) { + throw new IncompletePromptException(command); + } + undoCommand.setCanUndo(false, null); + lastAction = null; + return new GenerateReportCommand(commandParts); + case "undo": + undoCommand.allowExecute(lastAction); + lastAction = null; + return undoCommand; + case "quit": + return new ExitCommand(commandParts); + default: + throw new IncompletePromptException(command); + } + } +} 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/main/java/storage/BarChart.java b/src/main/java/storage/BarChart.java new file mode 100644 index 0000000000..2cddca678b --- /dev/null +++ b/src/main/java/storage/BarChart.java @@ -0,0 +1,59 @@ +package storage; + +import financialtransactions.Transaction; +import financialtransactions.TransactionList; +import java.util.HashMap; +import java.util.Map; + +//@@author ChongXern +public class BarChart> { + private static final int TOTAL_BAR_SIZE = 50; + private static final int LEGEND_SIZE = 10; + private TransactionList transactionList; + private int totalTransactionsCount; + private HashMap categoryFrequencies; + + public BarChart(TransactionList transactionList) { + this.transactionList = transactionList; + this.totalTransactionsCount = transactionList.getTransactionListSize(); + this.categoryFrequencies = new HashMap<>(); + //this.categoryPercentages = new HashMap<>(); + } + + public void computeTransactionFrequencies() throws Exception { + for (int i = 0; i < totalTransactionsCount; i++) { + String category = transactionList.getNthTransaction(i).getCategory().toString(); + if (categoryFrequencies.containsKey(category)) { + int categoryCount = categoryFrequencies.get(category); + categoryFrequencies.put(category, categoryCount + 1); + } else { + categoryFrequencies.put(category, 1); + } + } + } + + public String generateBarChart() throws Exception { + computeTransactionFrequencies(); + String barChartString = ""; + for (Map.Entry entry : categoryFrequencies.entrySet()) { + String category = entry.getKey(); + Integer frequency = entry.getValue(); + if (frequency == 0) { + continue; + } + double percentage = (double)frequency / totalTransactionsCount; + int barSize = (int)(percentage * TOTAL_BAR_SIZE); + System.out.print(category); + for (int i = 0; i < LEGEND_SIZE - category.length(); i++) { + barChartString += " "; + } + System.out.print(": "); + for (int i = 0; i < barSize; i++) { + barChartString += "|"; + } + percentage = Math.round(percentage * 10000.0) / 100.0; + barChartString += (" " + percentage + "%\n"); + } + return barChartString; + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..e26c71366e --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,137 @@ +package storage; + +import customexceptions.CategoryNotFoundException; +import financialtransactions.Inflow; +import financialtransactions.Outflow; +import financialtransactions.Reminder; +import financialtransactions.TransactionManager; +import user.BaseUser; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +import customexceptions.UserNotFoundException; + +public class Storage { + private final String filePath; + private Scanner sc; + public Storage(String filePath) { + this.filePath = filePath; + } + + //@@author chenhowy-unused + //This method is not used as TP should not need to support multiple users. + public void addNewUser(String username, String password) throws Exception { + try { + FileWriter fw = new FileWriter(filePath + "/passwords.txt", true); + fw.write(username + "|" + password + "\n"); + fw.close(); + } catch (IOException e) { + throw new Exception("Error adding user"); + } + } + + public BaseUser loadUser(String username) throws UserNotFoundException { + File f = new File(filePath + "/passwords.txt"); + try { + this.sc = new Scanner(f); + while (sc.hasNext()) { + String line = sc.nextLine(); + if (line.startsWith(username)) { + String password = line.split("\\|")[1]; + BaseUser newUser = new BaseUser(username, password); + return newUser; + } + } + throw new UserNotFoundException(); + } catch (FileNotFoundException e) { + createFileDir(); + throw new UserNotFoundException(); + } + } + //@@author + public BaseUser loadMockUser(){ + return new BaseUser("Bob", "password"); + } + + //@@author chenhowy + /** + * Returns a transaction manager object containing all the previous transactions that were in the save file + * The method will search for file "username.txt" in the ./data directory. + * If the file does not exist, it will create a ./data directory if it does not already exist. + * Otherwise, an empty transaction manager will be returned + * + * @param username the username for the file to be loaded + * @return Transaction Manager object with previous transactions loaded + * @throws CategoryNotFoundException If category does not exist. + */ + public TransactionManager loadFile(String username) throws CategoryNotFoundException { + File f = new File(filePath + String.format("/%s.txt", username)); + TransactionManager manager = new TransactionManager(); + Scanner sc; + try { + sc = new Scanner(f); + double budget = 0.00; + if (sc.hasNextLine()){ + budget = Double.parseDouble(sc.nextLine()); + } + manager.setBudget(budget); + while (sc.hasNext()) { + String[] transactionInfo = sc.nextLine().split("\\|"); + assert transactionInfo.length == 5 : "Transaction info should have 5 arguments"; + double amount = Double.parseDouble(transactionInfo[1]); + if (transactionInfo[4].equals("I")) { + Inflow inflow = new Inflow(transactionInfo[0], amount, transactionInfo[2]); + inflow.setCategory(transactionInfo[3]); + manager.addTransaction(inflow); + } else if (transactionInfo[4].equals("O")) { + Outflow outflow = new Outflow(transactionInfo[0], -amount, transactionInfo[2]); + outflow.setCategory(transactionInfo[3]); + manager.addTransaction(outflow); + } else { + Reminder reminder = new Reminder(transactionInfo[0], -amount, transactionInfo[2]); + reminder.setCategory(transactionInfo[3]); + manager.addTransaction(reminder); + } + } + sc.close(); + } catch (FileNotFoundException e) { + createFileDir(); + } + return manager; + } + + /** + * Creates a file directory ./data + * + * @return True if directory was created. + */ + private boolean createFileDir() { + File f = new File(filePath); + return f.mkdir(); + } + + /** + * Returns a string if file was saved successfully. + * The method calls toSave of transaction manager which generates a string containing all past transactions. + * This string will then be written to the file "username.txt". + * + * @param username The username of the current user + * @param tm The transaction manager of the current instance that contains the transactions + * @return A string if the file was saved + * @throws Exception If the file was unable to be saved. + */ + public String saveFile(String username, TransactionManager tm) throws Exception { + try { + FileWriter fw = new FileWriter(filePath + String.format("/%s.txt", username)); + fw.write(tm.toSave()); + fw.close(); + } catch (IOException e) { + throw new Exception("Error saving file"); + } + return "File saved..."; + } +} diff --git a/src/main/java/template/BaseDate.java b/src/main/java/template/BaseDate.java new file mode 100644 index 0000000000..6011fec001 --- /dev/null +++ b/src/main/java/template/BaseDate.java @@ -0,0 +1,96 @@ +package template; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Locale; +import java.util.ArrayList; +//@@author dylansiew +public class BaseDate { + //@@author dylansiew + public static DateTimeFormatter formatter = null; + + private static final List dateFormats = List.of( + "yyyy-MM-dd", + "dd-MM-yyyy", + "dd/MM/yyyy", + "yyyy-MM-dd", + "yyyy/MM/dd", + "dd:MM:yyyy", + "ddMMyyyy", + "ddMMyy", + "MMM dd yyyy"); + + private static final List timeFormats = List.of( + "HH:mm", + "HHmm", + "hh:mma"); + + private static final ArrayList dateTimeFormats = dateTimeVary(); + + private LocalDateTime dateTime = null; + + public BaseDate(String args) { + //@@author dylansiew + args = args.strip(); + if (!args.contains(" ")) { + String defaultTime = " 0000"; + args = args + defaultTime; + } + for (String format : dateTimeFormats) { + try { + formatter = DateTimeFormatter.ofPattern(format); + dateTime = LocalDateTime.parse(args, formatter); + } catch (DateTimeParseException e) { + continue; + } + + } + } + + private static ArrayList dateTimeVary() { + //@@author dylansiew + ArrayList varyList = new ArrayList<>(); + for (String dateFormat : dateFormats) { + for (String timeFormat : timeFormats) { + varyList.add(String.format("%s %s", dateFormat, timeFormat)); + varyList.add(String.format("%s %s", timeFormat, dateFormat)); + } + } + return varyList; + } + + @Override + public String toString() { + //@@author dylansiew + formatter = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mma", Locale.US); + return String.format("%s", dateTime.format(formatter)); + } + + public boolean equals(BaseDate otherDate) { + //@@author dylansiew + if (otherDate != null && this.dateTime != null) { + return this.dateTime.toLocalDate().equals(otherDate.dateTime.toLocalDate()); + } + return false; + } + + public boolean isBefore(BaseDate otherDate) { + //@@author dylansiew + if (otherDate != null && this.dateTime != null) { + return this.dateTime.isBefore(otherDate.dateTime); + } + return false; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + //@@author Kishen271828 + public int compareTo(BaseDate otherDate) { + return this.dateTime.compareTo(otherDate.dateTime); + } + +} diff --git a/src/main/java/user/Authentication.java b/src/main/java/user/Authentication.java new file mode 100644 index 0000000000..5042b8f53e --- /dev/null +++ b/src/main/java/user/Authentication.java @@ -0,0 +1,47 @@ +package user; + +import customexceptions.ExceededAttemptsException; +import customexceptions.ExitLoginException; +import userinterface.UI; + +public class Authentication { + private static final int attemptsLimit = 3; + String username; + private String password; + + public Authentication(String username, String password) { + this.password = password; + this.username = username; + } + + public boolean checkPassword(String username, String password) { + return this.password.equals(password) && this.username.equals(username); + } + + public boolean changePassword(String username, String oldPassword, String newPassword) + throws SecurityException, ExceededAttemptsException { + if (!checkPassword(username, oldPassword)) { + return false; + } + this.password = newPassword; + return true; + } + + public static boolean authenticateUser(BaseUser user, UI ui) throws ExceededAttemptsException, ExitLoginException { + Authentication auth = user.getAuthentication(); + String passwordInput; + for (int i = 0; i < Authentication.attemptsLimit; i++) { + ui.printMessage("Password: "); + passwordInput = ui.readInput(); + if (passwordInput.toLowerCase().equals("exit_login")){ + throw new ExitLoginException(); + } + if (auth.checkPassword(user.getUsername(), passwordInput)) { + return true; + } else { + ui.printMessage("Wrong password"); + } + } + throw new ExceededAttemptsException(); + } +} diff --git a/src/main/java/user/BaseUser.java b/src/main/java/user/BaseUser.java new file mode 100644 index 0000000000..25aa6e0620 --- /dev/null +++ b/src/main/java/user/BaseUser.java @@ -0,0 +1,34 @@ +package user; + +import financialtransactions.TransactionManager; + +public class BaseUser { + String username; + Authentication auth; + TransactionManager manager = null; + + public BaseUser(String username, String password) { + this.username = username; + this.auth = new Authentication(username, password); + } + + public String getUsername() { + return username; + } + + public boolean setTransactionManager(TransactionManager manager){ + if(this.manager == null){ + this.manager = manager; + return true; + } + return false; + } + + public TransactionManager getTransactionManager(){ + return this.manager; + } + + public Authentication getAuthentication(){ + return this.auth; + } +} diff --git a/src/main/java/user/InactivityTimer.java b/src/main/java/user/InactivityTimer.java new file mode 100644 index 0000000000..ee42db36fb --- /dev/null +++ b/src/main/java/user/InactivityTimer.java @@ -0,0 +1,33 @@ +package user; + +import customexceptions.InactivityTimeoutException; + +//@@author ChongXern +public class InactivityTimer { + private static final int INACTIVITY_TIME = 180_000; + private static final int GRACE_TIME = 30_000; + public long startTime; + + public InactivityTimer() { + startTime = System.currentTimeMillis(); + } + + public void resetTimer() { + assert startTime != System.currentTimeMillis(); + startTime = System.currentTimeMillis(); + } + + public void checkTimeElapsed() throws InactivityTimeoutException { + long timeDifference = System.currentTimeMillis() - startTime; + if (timeDifference >= INACTIVITY_TIME) { + throw new InactivityTimeoutException(true, false); + } else if (timeDifference >= INACTIVITY_TIME - GRACE_TIME) { + throw new InactivityTimeoutException(false, true); + } + } + + public void checkTimeFromStart() { + long timeDifference = System.currentTimeMillis() - startTime; + System.out.println(timeDifference + "ms has passed"); + } +} diff --git a/src/main/java/userinterface/UI.java b/src/main/java/userinterface/UI.java new file mode 100644 index 0000000000..b21e7dd01d --- /dev/null +++ b/src/main/java/userinterface/UI.java @@ -0,0 +1,30 @@ +package userinterface; + +import java.util.Scanner; + +public class UI { + private final Scanner sc = new Scanner(System.in); + + public UI() { + } + + public void printMessage(String message) { + System.out.println(message); + } + + public void printLine() { + System.out.println("____________________________________________________________________"); + } + + public String readInput() { + if (sc.hasNextLine()) { + return sc.nextLine(); + } + return null; + + } + + public void closeScanner() { + sc.close(); + } +} diff --git a/src/test/data/passwords.txt b/src/test/data/passwords.txt new file mode 100644 index 0000000000..b82d33347d --- /dev/null +++ b/src/test/data/passwords.txt @@ -0,0 +1 @@ +Bob|password diff --git a/src/test/java/command/AddInflowCommandTest.java b/src/test/java/command/AddInflowCommandTest.java new file mode 100644 index 0000000000..89d88ae275 --- /dev/null +++ b/src/test/java/command/AddInflowCommandTest.java @@ -0,0 +1,24 @@ +package command; + +import org.junit.jupiter.api.Test; + +import financialtransactions.TransactionManager; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddInflowCommandTest { + @Test + public void testAddInflow() { + String arg = "add-inflow n/Salary a/400.00 d/23/05/2022 t/1900 c/income"; + String[] splitCommand = arg.split(" "); + try{ + AddInflowCommand command = new AddInflowCommand(splitCommand); + TransactionManager manager = new TransactionManager(); + assertEquals(command.execute(manager), "Ok. Added inflow"); + } catch (Exception e){ + System.out.println(e.getMessage()); + } + + + } +} diff --git a/src/test/java/command/AddOutflowCommandTest.java b/src/test/java/command/AddOutflowCommandTest.java new file mode 100644 index 0000000000..1d871e8c6a --- /dev/null +++ b/src/test/java/command/AddOutflowCommandTest.java @@ -0,0 +1,23 @@ +package command; + +import org.junit.jupiter.api.Test; + +import financialtransactions.TransactionManager; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddOutflowCommandTest { + @Test + public void testAddOutflowTest(){ + String arg = "add-outflow n/Rent a/1500.00 d/23/06/2023 t/1800 c/rent"; + String[] splitCommand = arg.split(" "); + try{ + AddOutflowCommand addOutflowCommandcommand = new AddOutflowCommand(splitCommand); + TransactionManager manager = new TransactionManager(); + assertEquals(addOutflowCommandcommand.execute(manager), "Ok. Added outflow"); + } catch (Exception e){ + System.out.println(e.getMessage()); + } + + } +} diff --git a/src/test/java/financialtransactions/InflowTest.java b/src/test/java/financialtransactions/InflowTest.java new file mode 100644 index 0000000000..fe25d2c7aa --- /dev/null +++ b/src/test/java/financialtransactions/InflowTest.java @@ -0,0 +1,19 @@ +package financialtransactions; + +import customexceptions.CategoryNotFoundException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class InflowTest { + @Test + public void testSetCategory() { + Inflow inflow = new Inflow("February salary", 20.00, "2024-03-02"); + try { + inflow.setCategory("Income"); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + assertEquals(Inflow.Category.INCOME, inflow.getCategory()); + } +} diff --git a/src/test/java/financialtransactions/OutflowTest.java b/src/test/java/financialtransactions/OutflowTest.java new file mode 100644 index 0000000000..53d1e3d0e9 --- /dev/null +++ b/src/test/java/financialtransactions/OutflowTest.java @@ -0,0 +1,12 @@ +package financialtransactions; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OutflowTest { + @Test + public void testIsValidCategory(){ + Outflow outflow = new Outflow("2024 Sem 2 School Fees", 999999.99, "2024-04-01"); + assertTrue(outflow.isValidCategory("EDUCATION")); + } +} diff --git a/src/test/java/financialtransactions/TransactionManagerTest.java b/src/test/java/financialtransactions/TransactionManagerTest.java new file mode 100644 index 0000000000..082f99ef2f --- /dev/null +++ b/src/test/java/financialtransactions/TransactionManagerTest.java @@ -0,0 +1,44 @@ +package financialtransactions; + +import customexceptions.CategoryNotFoundException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TransactionManagerTest { + @Test + public void toSaveTest() { + TransactionManager managerTest = new TransactionManager(); + + Inflow income = new Inflow("Salary payment", 400.00, "23/05/2022 1900"); + try { + income.setCategory("INCOME"); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + managerTest.addTransaction(income); + + Outflow shopping = new Outflow("Shopping", 200, "23/05/2022 2000"); + try { + shopping.setCategory("Shopping"); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + managerTest.addTransaction(shopping); + + Reminder bill = new Reminder("Water bills", 64.30, "25/06/2025 1500"); + try { + bill.setCategory("utilities"); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + managerTest.addTransaction(bill); + + managerTest.setBudget(1500); + + assertEquals("1500.00\n" + + "Salary payment|400.00|May 23 2022 07:00PM|INCOME|I\n" + + "Shopping|-200.00|May 23 2022 08:00PM|SHOPPING|O\n" + + "Water bills|-64.30|Jun 25 2025 03:00PM|UTILITIES|R\n", managerTest.toSave()); + } +} diff --git a/src/test/java/parser/ParserTest.java b/src/test/java/parser/ParserTest.java new file mode 100644 index 0000000000..946de21983 --- /dev/null +++ b/src/test/java/parser/ParserTest.java @@ -0,0 +1,39 @@ +package parser; + +import command.BaseCommand; +import financialtransactions.TransactionManager; +import org.junit.jupiter.api.Test; + +import command.AddInflowCommand; +import command.AddOutflowCommand; +import userinterface.UI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +public class ParserTest { + Parser parser = new Parser(new UI()); + TransactionManager manager = new TransactionManager(); + + @Test + public void addInflow_success() throws Exception { + String command = "add-inflow n/Stocks a/700 d/28/07/2019 t/1300 c/investment"; + BaseCommand test1 = parser.parseCommand(command); + assertEquals("Ok. Added inflow", test1.execute(manager)); + } + + @Test + public void addOutflow_success() throws Exception { + String command = "add-outflow n/Rent a/2000 d/29/09/2021 t/1100 c/rent"; + BaseCommand test1 = parser.parseCommand(command); + assertEquals("Ok. Added outflow", test1.execute(manager)); + } + + @Test + public void sampleTest() throws Exception{ + String assert1 = "add-inflow n/Salary a/400.00 d/23/05/2022 t/1900 c/income"; + String assert2 = "add-outflow n/Rent a/1500.00 d/23/06/2023 t/1800 c/rent\n"; + assertInstanceOf(AddInflowCommand.class, parser.parseCommand(assert1)); + assertInstanceOf(AddOutflowCommand.class, parser.parseCommand(assert2)); + } +} diff --git a/src/test/java/seedu/OutflowTest.java b/src/test/java/seedu/OutflowTest.java new file mode 100644 index 0000000000..de080dbcaf --- /dev/null +++ b/src/test/java/seedu/OutflowTest.java @@ -0,0 +1,19 @@ +package seedu; + +import customexceptions.CategoryNotFoundException; +import financialtransactions.Outflow; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OutflowTest { + @Test + public void testSetCategory() { + Outflow outflow = new Outflow("2024 Sem 2 School Fees", 999999.99, "2024-02-01"); + try { + outflow.setCategory("Education"); + } catch (CategoryNotFoundException e) { + System.out.println(e.getMessage()); + } + assertEquals(Outflow.Category.EDUCATION, outflow.getCategory()); + } +} 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/src/test/java/user/BaseUserTest.java b/src/test/java/user/BaseUserTest.java new file mode 100644 index 0000000000..032288d176 --- /dev/null +++ b/src/test/java/user/BaseUserTest.java @@ -0,0 +1,15 @@ +package user; + +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BaseUserTest { + @Test + public void baseUserTest() { + BaseUser user = new BaseUser("Bob", "password"); + Authentication auth = user.getAuthentication(); + assertTrue(auth.checkPassword("Bob", "password")); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..eaa015f1be 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,42 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +Welcome. Enter your username and password to login. +username: +password: +Password is correct. You are now logged in +Ok. Added inflow +Ok. Added outflow +Ok. Added inflow +Inflows: +Transactions: +1) Name: Stocks, Amount: 400.00, Date: Oct 24 2024 12:00PM + +Outflows: +Transactions: +1) Name: Rent, Amount: -1500.00, Date: Jun 23 2023 06:00PM + +Ok. Added outflow +Ok. Added inflow +Inflows: +Transactions: +1) Name: Refund, Amount: 50.00, Date: Jun 23 2023 06:00PM +2) Name: Stocks, Amount: 400.00, Date: Oct 24 2024 12:00PM +3) Name: Salary, Amount: 400.00, Date: May 23 2022 07:00PM + +Outflows: +Transactions: +1) Name: Groceries, Amount: -100.00, Date: Jun 23 2023 06:00PM +2) Name: Rent, Amount: -1500.00, Date: Jun 23 2023 06:00PM + +Ok. Inflow deleted +Ok. Outflow deleted +Ok. Edited inflow +Ok. Edited outflow +Inflows: +Transactions: +1) Name: Refund, Amount: 100.00, Date: Jun 23 2023 05:00PM +2) Name: Stocks, Amount: 400.00, Date: Oct 24 2024 12:00PM + +Outflows: +Transactions: +1) Name: Groceries, Amount: -150.00, Date: Jun 23 2023 06:30PM + +Exiting application diff --git a/text-ui-test/data/passwords.txt b/text-ui-test/data/passwords.txt new file mode 100644 index 0000000000..b82d33347d --- /dev/null +++ b/text-ui-test/data/passwords.txt @@ -0,0 +1 @@ +Bob|password diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..8e33dfd304 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,15 @@ -James Gosling \ No newline at end of file +Bob +password +add-inflow n/Salary a/400.00 d/23/05/2022 t/1900 c/income +add-outflow n/Rent a/1500.00 d/23/06/2023 t/1800 c/rent +add-inflow n/Stocks a/400.00 d/24/10/2024 t/1200 c/investment +view-history n/2 +add-outflow n/Groceries a/100.00 d/23/06/2023 t/1800 c/shopping +add-inflow n/Refund a/50.00 d/23/06/2023 t/1800 c/refund +view-history n/5 +delete-inflow i/3 +delete-outflow i/2 +edit-inflow i/1 n/Refund a/100.00 d/23/06/2023 t/1700 c/refund +edit-outflow i/1 n/Groceries a/150.00 d/23/06/2023 t/1830 c/shopping +view-history n/3 +quit