diff --git a/.gitignore b/.gitignore index f69985ef1f..a9a3d4cd12 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +data.txt +/src/main/java/META-INF diff --git a/README.md b/README.md index 46933fbeb2..1576e97901 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Duke project template -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. +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 ## Setting up in Intellij diff --git a/build.gradle b/build.gradle index b0c5528fb5..19c34eef0d 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ test { } application { - mainClassName = "seedu.duke.Duke" + mainClassName = "seedu.mindmymoney.MindMyMoney" } shadowJar { @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..d515fc06c4 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +| Display | Name | Github Profile | Portfolio | +|:--------------------------------------------------:|:---------------------|:-------------------------------------------:|:-------------------------------------:| +| ![](./images/Dan_Profile_Picture.png) | Dan Baterisna | [Github](https://github.com/danbaterisna) | [Portfolio](./team/danbaterisna.md) | +| ![](./images/Glendon_Profile_Picture.png) | Ng Zhao Zhi, Glendon | [Github](https://github.com/GlendonNotGlen) | [Portfolio](./team/glendonnotglen.md) | +| ![](./images/LimJieRui_Profile_Picture(Final).png) | Lim Jie Rui | [Github](https://github.com/limjierui) | [Portfolio](./team/limjierui.md) | +| ![](./images/KitHan_Profile_Picture.png) | Seah Kit Han | [Github](https://github.com/khseah) | [Portfolio](./team/khseah.md) | +| ![](./images/Sean_Profile_Picture.png) | Sean Ho Wen Bin | [Github](https://github.com/SeanHoWB) | [Portfolio](./team/seanhowb.md) | diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..105c52cf5b 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,906 @@ -# Developer Guide +# MindMyMoney Developer Guide -## Acknowledgements +## Content Page +* [Content Page](#content-page) +* [Introduction](#introduction) + * [Purpose](#purpose) + * [Acknowledgements](#acknowledgements) + * [Using the Developer Guide](#using-the-developer-guide) +* [Design](#design) + * [Architecture Overview](#architecture-overview) + * [Component Overview ](#component-overview ) + * [UI component](#ui-component) + * [Parser component](#parser-component) + * [Command component](#command-component) + * [Storage component](#storage-component) +* [Implementation](#implementation) + * [Add Command](#add-command) + * [Add Expenditure](#add-expenditure-e) + * [Add Credit Card](#add-credit-card-cc) + * [Add Income](#add-income-i) + * [AddCommand Design Considerations](#addcommand-design-considerations) + * [CalculateInput Command](#calculateinputcommand-feature) + * [CalculateInputCommand Design Considerations](#calculateinputcommand-design-considerations) + * [List Command](#list-command) + * [List Expenditure](#list-expenditure-e) + * [List Credit Card](#list-credit-card-cc) + * [List Income](#list-income-i) + * [ListCommand Design Considerations](#list-command-design-considerations) + * [Delete Command](#delete-command) + * [Delete Expenditure](#delete-expenditure-e) + * [Delete Credit Card](#delete-credit-card-cc) + * [Delete Income](#delete-income-i) + * [DeleteCommand Design Considerations](#delete-command-design-considerations) + * [Update Command](#update-command) + * [Update Expenditure](#update-expenditure-e) + * [Update Credit Card](#update-credit-card-cc) + * [Update Income](#update-income-i) + * [UpdateCommand Design Considerations](#update-command-design-considerations) + * [Storage](#storage) + * [Loading](#loading) + * [Saving](#saving) + * [StorageCommand Design Considerations](#storage-design-considerations) +* [Appendix Requirements](#appendix-requirements) + * [Product scope](#product-scope) + * [User Stories](#user-stories) + * [Non-Functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) +* [Instructions for manual testing](#instructions-for-manual-testing) -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +
-## Design & implementation +## Introduction -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +**MindMyMoney** (M3) is a desktop app for managing users' personal finances, optimized for use via a +Command Line Interface (CLI). With the application, users can track their expenses across multiple payment methods, +calculate monthly expenditure, and set financial goals. The application is targeted at students looking to start +managing their personal finances. +
-## Product scope -### Target user profile +### Purpose +This document specifies the architecture and software design decisions for the application, MindMyMoney. +The intended audience of this document is the developers, designers, and software testers of MindMyMoney. -{Describe the target user profile} +
-### Value proposition +### Acknowledgements +We would like to thank [AddressBook-3](https://se-education.org/addressbook-level3/) for assisting us in developing +MindMyMoney. -{Describe the value proposition: what problem does it solve?} +
-## User Stories +### Using the Developer Guide +Along the guide you may encounter several icons. These icons will provide several useful information. +> **💡 Note:** +>- This tells you that there is additional information that is useful when you are using the application. -|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| +> **⚠️Warning⚠️** +> - This tells you that there is some **important** information you should take note of to prevent issues from arising + when you are using the application. -## Non-Functional Requirements +Click on the hyperlinks in the [content page](#content-page) to quickly navigate the developer's guide. -{Give non-functional requirements} +
-## Glossary +## Design +**MindMyMoney** is written fully in **Java 11** using Object-Oriented Programming (OOP) paradigm to help structure and +organise the code. This enables the efficiency of future improvements and revisions. +Data stored in the application is saved into text files locally on the user's device. -* *glossary item* - Definition +
+ +### Architecture Overview +![architecture diagram](images/ArchitectureDiagramFinal.png) +Fig 1 - Architecture Diagram for MindMyMoney + +The Architecture Diagram above shows the high-level design of the application. The **main components** +consist of: +- `MMM`: Initialises the components in the correct sequence and connects them with each other. Also holds the user's +expenditures in memory. +- `Ui`: The User Interface of the application and deals with interaction with the user. +- `Parser`: Deals with making sense of user commands. +- `Commands`: The collection of all executable commands. +- `Storage`: Reads data from, and writes data to the hard disk. + +By abstracting out closely related code into classes, it allows `MMM` to deal at a higher level, without worrying +about the lower level details. Higher cohesion is also achieved and coupling is minimized. + +
+ +The Sequence Diagram below shows an example of how the components interact with each other for the scenario +where the user issues the command `add /e /pm cash /c food /d Porridge /a 3 /t 04/04/2022` to add an expenditure. + +![sequence_diagram](images/ComponentsSequenceDiagramFinal.png) +
Fig 2 - Sequence Diagram showing the Add Command + +
+ +### Component Overview +The major code is broken down into components for better abstraction. +The sections below give more details for each component. + +
+ +### UI component +The source code can be found in [`Ui.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/Ui.java) + +![ui_diagram](images/UiClassDiagram.png) +
Fig 3 - Ui Class Diagram + +The UI component consists of a `Ui` and `PrintStrings` class. + +The UI component: +- Prints the welcome banner and message on startup, as well as a financial tip from the `PrintStrings` class. +- Prints `>` before the user's input, to help the user differentiate between their input and an output from the +application. +- Reads input from the user. + +
+ +### Parser component +The source code can be found in [`Parser.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/Parser.java) + +![parser_diagram](images/ParserClassDiagram.png) +
Fig 4 - Parser Class Diagram + +The Parser component consists of a `Parser` and `User` class. The `User` class further consists of an `ExpenditureList`, +`CreditCardList` and `IncomeList` class, which makes use of the `Expenditure`, `CreditCard` and `Income` class respectively. + +The Parser component: +- Receives user's input and splits it into the Command Type and Description using the `GeneralFunctions` class. +- Uses the `User` class and user's input to instantiate a `Command` object based on the Command Type. +- Returns the `Command` object that can then be executed. + +We pass in the `User` class to the `Command` object instead of using a global variable to ease testing. This way, we can +add, delete and update entries in a new `User` during testing without affecting the actual `User`. + +
+ +### Command component +The source can be found in [`command`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/command) + +![command_diagram](images/CommandClassDiagram.png) +Fig 5 - Command Class Diagram + +The Command component consists of Command abstract class, `AddCommand`, `ByeCommand`, `DeleteCommand`, `HelpCommand` +,`CalculateInputCommand`, `ListCommand`and `UpdateCommand` that extends the `Command` class. + +The Command component: +- Provides all the Command classes which can be instantiated by `Parser.parseCommand()`. The Command objects can then be +executed. Only 1 Command object can be created. +- Includes a `Command.executeCommand()` method in each Command classes which performs the relevant command and throws +exceptions if an error is encountered. The error is then handled. + +
+ +### Storage component +The source can be found in [`Storage.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/Storage.java) + +![storage_diagram](images/StorageClassDiagram.png) +Fig 6 - Storage Class Diagram + +The Storage component consists of `Storage` class. + +The Storage component: +- `MMM` class initialises a `Storage` object upon start up. The `Storage` class consists of +`Storage.load()` and `Storage.save()` methods. +- Concurrently, `MMM` will call the `Storage.load()` method and load any data that is stored on the hard disk. +- `MMM` calls the `Storage.save()` method and stores remaining data onto the hard disk when the program exits. + +
+ +## Implementation +This section describes some noteworthy details on how certain features of MindMyMoney are implemented. + +
+ +### Add Command +The source code can be found in [`AddCommand.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/command/AddCommand.java) + +The Add Command feature allows users to add expenditures, credit cards or their income using a single command. +This provides speed and ease of use by only requiring a single line of input. + +The Add Command has 3 parts. These parts are differentiated by their flags: +- Add expenditure `/e`. +- Add credit card `/cc`. +- Add income `/i`. + +The sequence diagram below shows the interactions when an `AddCommand` is executed. + +![add_command_sequence_diagram](images/AddCommandSequenceDiagram.png) +Fig 7 - AddCommand Sequence Diagram + +Below is an example showing how the AddCommand behaves at each step. +1. The `Parser` component parses user input and returns the new `AddCommand` object to the + `MindMyMoney`. +2. `AddCommand` instantiates `addInput`, `expenditureList`, `creditCardList`, `incomeList`. +3. The application invokes `AddCommand.executeCommand()` to execute user instruction. +4. If user input contains `/e`, the application executes `AddCommand.addExpenditure()`. +5. Else user input contains `/cc`, the application executes `AddCommand.addCreditCard()`. +6. Else if user input contains `/i`, the application executes `AddCommand.addIncome()`. +7. The application then returns to the Parser component. + +
+ +#### Add Expenditure `/e` +A key functionality of MindMyMoney is the ability to add and track user expenditure. Expenditures are added through +the `AddCommand.addExpenditure()` method, invoked when using the `/e` flag. Additional parameters `PAYMENT_METHOD`, +`CATEGORY`, `DESCRIPTION`, `AMOUNT` and `TIME` are also required. + + +![add_expenditure_command_sequence_diagram](images/AddExpenditureSequenceDiagram.png) +Fig 8 - Add Expenditure Command Sequence Diagram + +The sequence diagram above shows the interactions of different classes when adding an expenditure +to the list. + +1. During the execution, `AddCommand.addExpenditure()` will parse through user input to obtain the `PAYMENT_METHOD`, `CATEGORY`, + `DESCRIPTION`, `AMOUNT` and `TIME` fields. +2. Once all the fields are obtained, `AddCommand.addExpenditure()` will run tests for its respective fields. +3. The `AddCommand.addExpenditure()` object formats the `CATEGORY` and `AMOUNT` fields, and the `PAYMENT_METHOD` if it is cash. +4. The `AddCommand.addExpenditure()` object instantiates a new `Expenditure` object with the 5 fields and adds them + into the `ExpenditureList`. +5. The `AddCommand.addExpenditure()` object prints a list to show the user what it has saved. +6. If user input for `PAYMENT_METHOD` is not cash, `AddCommand.addExpenditure()` invokes `AddCommand.updateCreditCardTotalExpenditure` to update the corresponding `creditCard.totalExpenditure` +7. The `AddCommand.addExpenditure()` returns to `AddCommand`. + +
+ +#### Add Credit Card `/cc` +MindMyMoney allows users to track their different credit cards. Credit cards are added through the `AddCommand.addCreditCard()` +method, invoked when using the `/cc` flag. Additional parameters `CREDIT_CARD_NAME`, `CASHBACK` and `CARD_LIMIT` are also +required. + +![add_credit_card_command_sequence_diagram](images/AddCreditCardSequenceDiagram.png) +Fig 9 - Add Credit Card Command Sequence Diagram + +The sequence diagram above shows the interactions of different classes when adding a credit card to the list. + +1. During the execution, `AddCommand.addCreditCard()` will parse through user input to obtain the `CREDIT_CARD_NAME`, `CATEGORY`, + `CASH_BACK` and `CARD_LIMIT` fields. +2. Once all the fields are obtained, `AddCommand.addExpenditure()` will run tests for its respective fields. +3. The `AddCommand.addCreditCard()` object instantiates a new `CreditCard` + object with the aforementioned 3 fields and adds them into the `CreditCardList`. +4. The `AddCommand.addCreditCard()` object prints a list to show the user what it has saved. +5. The `AddCommand.addCreditCard()` returns to `AddCommand`. + +
+ +#### Add Income `/i` +MindMyMoney allows users to track their sources of income. Incomes are added through the `AddCommand.addIncome()` +method, invoked when using the `/i` flag. Additional parameters `AMOUNT` and `CATEGORY` are also required. + +![add_income_sequence_diagram](images/AddIncomeSequenceDiagram.png) +
Fig 10 - Add Income Sequence Diagram + +The sequence diagram above shows the interactions of different classes when adding an income to the list. + +1. After receiving the `AddCommand` object from `Parser`, `MMM` calls the `AddCommand.executeCommand()` method. +2. `AddCommand.addIncome()` method is invoked as the `/i` flag is present. It parses through the user's input to obtain +`AMOUNT` and `CATEGORY` fields. It also runs tests on these fields to ensure the inputs are valid. +3. An `Income` object is instantiated using the aforementioned fields and is added into the `IncomeList`. +4. Control is returned to `MMM`. + +
+ +#### AddCommand Design Considerations +Aspect: How to ask user for the fields of input. +* Alternative 1 (current choice): User is asked to put in all fields at once, separated using flags. + * Pros: Faster input, user can enter an expenditure using a single input. + * Cons: User must be able to remember all the flags and its sequence. + +* Alternative 2: User is asked iteratively to put in all fields, prompted by a message after each input. + * Pros: Beginner friendly, easily understandable, no need to remember flags. + * Cons: Slower, implementation when user is familiar with the application. + +
+ +### CalculateInputCommand feature +The source code can be found in [`CalculateInputCommand.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/command/CalculateInputCommand.java) +and [`Calculations.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/helper/Calculations.java) + +MindMyMoney allows users to view their finances in a more meaningful manner by displaying the total expenditure and breakdown of expenses in a +bar chart format. + +The CalculateCommand can take in 3 different `[DATE]` fields: +- `calculate /epm [DD/MM/YYYY]` allows user to calculate total expenditure of the specific date. +- `calculate /epm [MM/YYYY]` allows user to calculate total expenditure of the specific month. +- `calculate /epm [YYYY]` allows user to calculate total expenditure of the specific year. + +
+ +![calculate_command_sequence_diagram](images/CalculateCommandSequenceDiagramFinal.png) +
Fig 11 - Calculate Input Command Sequence Diagram + +The sequence diagram above shows the interactions of different classes when calculating the expenditure. + +1. After receiving the `CalculateInputCommand` object from `Parser`, `MMM` calls the `CalculateInputCommand.executeCommand()` method. +2. `GeneralFunctions.parseInput()` method is invoked to obtain the flag and date of the input. +3. If `/epm` flag is present. It calls `Calculations.calculateExpenditure()` method to obtain the total expenditure of the date specified. +4. During the execution of `Calculations.calculateExpenditure()`, the `GeneralFunctions.findItemInList()` is invoked to +find the items that contain the specified date. +5. Afterwards, `Calculations.displayCalculationBreakdown()` is invoked to show the breakdown of expenses in a bar chart format. +6. If `/epm` flag is not present, MindMyMoneyException is thrown. + +
+ +#### CalculateInputCommand Design Considerations +Aspect: How to allow users to have a better understanding of their own expenses. +* User is required to input either the date, month or year in order to calculate their expenses. + * Pros: User can have a better understanding of their expenditure breakdowns by the specified time they want to look for. + * Cons: User is required to fill in a date, and the date must be found in the list of expenditures. + +* Use of bar chart to represent breakdown of expenses in the CalculateInputCommand. + * Pros: Users can view their overall expenses in a bar chart format, which is easier to view at one glance. + * Cons: Some users may not prefer to visualise their data in a bar chart format. + +
+ +### List Command +The source code can be found in [`ListCommand.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/command/ListCommand.java) + +The List Command feature allows users to view their current expenditures, credit cards and incomes, using their +respective flags: +- List expenditure `/e`. +- List credit card `/cc`. +- List income `/i`. + +![list_command_sequence_diagram](images/ListCommandSequenceDiagram.png) +
Fig 12 - List Command Sequence Diagram + +The sequence diagram above shows the interactions when a `ListCommand` is executed. +1. After receiving the `ListCommand` object from `Parser`, `MMM` calls the `ListCommand.executeCommand()` method. +2. If the expenses flag `/e` is present, it calls the `printExpenditureList()` method. +3. Else if the credit card flag `/cc` is present, it calls the `printCreditCardList()` method. +4. Else if the income flag `/i` is present, it calls the `printIncomeList()` method. +5. Else, it throws an error, which is then handled by printing an error message to the user. + +
+ +#### List Expenditure `/e` +Allows the user to view their list of current expenditures. The list is printed through the `ListCommand.printExpenditureList()` +method, invoked when using the `/e` flag. + +![list_expenditure_sequence_diagram](images/ListExpenditureSequenceDiagram.png) +
Fig 13 - List Expenditure Sequence Diagram + +The sequence diagram above shows the interactions when listing expenditures. +1. After receiving the `ListCommand` object from `Parser`, `MMM` calls the `ListCommand.executeCommand()` method. +2. `ListCommand.printExpenditureList()`method is invoked as the `/e` flag is present. +3. If the optional parameter `{DATE}` is not present, `ListCommand.listString()` method is invoked. This loops through +`expenditureList` and concatenates each expenditure entry into a String `listInString`. +4. Else if `{DATE}` is present, `ListCommand.listStringWithDate()` method is invoked. This loops through `expenditureList` +but only concatenates expenditure entries corresponding to the provided `{DATE}` into `listInString`. +5. The `listInString` is returned which is then printed out. +6. Control is returned to `MMM`. + +
+ +#### List Credit Card `/cc` +Allows the user to view their list of current credit cards. The list is printed through the `ListCommand.printCreditCardList()` +method, invoked when using the `/cc` flag. + +![list_credit_card_sequence_diagram](images/ListCreditCardSequenceDiagram.png) +
Fig 14 - List Credit Card Sequence Diagram + +The sequence diagram above shows the interactions when listing credit cards. +1. After receiving the `ListCommand` object from `Parser`, `MMM` calls the `ListCommand.executeCommand()` method. +2. `ListCommand.printCreditCardList()`method is invoked as the `/e` flag is present. +3. `ListCommand.creditCardListToString()` method is then invoked, which loops through the `creditCardList` and + concatenates each credit card into a String `listInString`. +4. The `listInString` is returned which is then printed out. +5. Control is returned to `MMM`. + +
+ +#### List Income `/i` +Allows the user to view their list of current incomes. The list is printed through the `ListCommand.printIncomeList()` +method, invoked when using the `/i` flag. + +![list_income_sequence_diagram](images/ListIncomeSequenceDiagram.png) +
Fig 15 - List Income Sequence Diagram + +The sequence diagram above shows the interactions when listing incomes. +1. After receiving the `ListCommand` object from `Parser`, `MMM` calls the `ListCommand.executeCommand()` method. +2. `ListCommand.printIncomeList()`method is invoked as the `/i` flag is present. +3. `ListCommand.incomeListToString()` method is then invoked, which loops through the `incomeList` and + concatenates each income entry into a String `listInString`. +4. The `listInString` is returned which is then printed out. +5. Control is returned to `MMM`. + +
+ +#### List Command design considerations +Aspect: To ease testing of `ListCommand` using JUnit. +* Alternative 1 (current choice): Abstract the conversion of `Expenditure`, `CreditCard` and `Income` to `String` in a +separate `ListCommand.listToString()` method. + * Pros: Easily tested using JUnit by checking the String that the `ListCommand.listToString()` method returns. + * Cons: Added layer of abstraction that may be deemed redundant. + +* Alternative 2: Print directly in the `ListCommand.executeCommand()` method. + * Pros: Easily implemented with lesser lines of code. + * Cons: JUnit testing would require I/O redirection prior to checking the output matches expectations. + +
+ + +### Delete Command +The source code can be found in [`DeleteCommand.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/command/DeleteCommand.java) + +The List Command feature allows users to delete an entry in their current `Expenditure`, `Credit Card` or `Income` list, using their +respective flags and followed by the index of the entry to be deleted: +- Delete an expenditure `/e [INDEX]`. +- Delete a credit card `/cc [INDEX]`. +- Delete an income `/i [INDEX]`. + +![delete_command_sequence_diagram](images/DeleteCommandSequenceDiagram.png) +
Fig 16 - Delete Command Sequence Diagram + +The sequence diagram above shows the interactions when a `DeleteCommand` is executed. +1. After receiving the `DeleteCommand` object from `Parser`, `MMM` calls the `DeleteCommand.executeCommand()` method. +2. If the expenses flag `/e` is present, it calls the `deleteExpenditure()` method. +3. Else if the credit card flag `/cc` is present, it calls the `deleteCreditCard()` method. +4. Else if the income flag `/i` is present, it calls the `deleteIncome()` method. +5. Else, it throws an error, which is then handled by printing an error message to the user. + +
+ +#### Delete Expenditure `/e` +Deletes an `Expenditure` specified by the user using the `Expenditure`'s index. The `Expenditure` is deleted through the `DeleteCommand.deleteExpenditure()` +method, invoked when using the `/e` flag. + +![delete_expenditure_sequence_diagram](images/DeleteExpenditureSequenceDiagramFinal.png) +
Fig 17 - Delete Expenditure Sequence Diagram + +The sequence diagram above shows the interactions when deleting an expenditure. +1. After receiving the `DeleteCommand` object from `Parser`, `MMM` calls the `DeleteCommand.executeCommand()` method. +2. `DeleteCommand.deleteExpenditure()`method is invoked as the `/e` flag is present. +3. `expenditureList.get()` method is then invoked, which retrieves the `Expenditure` to be deleted. +4. `Expenditure.getPaymentMethod()` is then used to check if the payment method was in `CASH`. +5. For Credit Card payment methods, the amount of expenditure is deducted from `CreditCard.totalExpenditure`. +6. Details of the deleted `Expenditure` is then printed out. +7. Control is returned to `MMM`. + +
+ +#### Delete Credit Card `/cc` +Deletes a `Credit Card` specified by the user using the `Credit Card`'s index. The `Credit Card` is deleted through the `DeleteCommand.deleteCreditCard()` +method, invoked when using the `/cc` flag. + +![Delete_credit_card_sequence_diagram](images/DeleteCreditCardSequenceDiagram.png) +
Fig 18 - Delete Credit Card Sequence Diagram + +The sequence diagram above shows the interactions when deleting a `Credit Card`. +1. After receiving the `DeleteCommand` object from `Parser`, `MMM` calls the `DeleteCommand.executeCommand()` method. +2. `DeleteCommand.deleteCreditCard()`method is invoked as the `/cc` flag is present. +3. `creditCardList.delete()` method is then invoked, which removes the `Credit Card` specified by the user. +4. Details of the deleted `Credit Card` is then printed out. +5. Control is returned to `MMM`. + +
+ + +#### Delete Income `/i` +Deletes an `Income` specified by the user using the `Income`'s index. The `Income` is deleted through the `DeleteCommand.deleteIncome()` +method, invoked when using the `/i` flag. + +![Delete_income_sequence_diagram](images/DeleteIncomeSequenceDiagramFinal.png) +
Fig 19 - Delete Income Sequence Diagram + +The sequence diagram above shows the interactions when deleting an `Income`. +1. After receiving the `DeleteCommand` object from `Parser`, `MMM` calls the `DeleteCommand.executeCommand()` method. +2. `DeleteCommand.deleteIncome()`method is invoked as the `/i` flag is present. +3. `IncomeList.delete()` method is then invoked, which removes the `Income` specified by the user. +4. Details of the deleted `Income` is then printed out. +5. Control is returned to `MMM`. + +
+ + +#### Delete Command design considerations +Aspect: To print deleted object from User's list. +* Alternative 1 (current choice): Prints details of deleted `Expenditure`, `Credit Card`, or `Income`. + * Pros: Users can easily verify that they have correctly deleted the object as intended. + * Cons: Printing extraneous lines onto the Command-Line may affect user-experience and find the output overwhelming. + +* Alternative 2: Quietly delete the specified `Expenditure`, `Credit Card`, or `Income`. + * Pros: Easily implemented. + * Cons: Users are unable to verify that the correct object was deleted. + +
+ +### Update Command +The source code can be found in [`UpdateCommand.java`](https://github.com/AY2122S2-CS2113T-T10-4/tp/blob/master/src/main/java/seedu/mindmymoney/command/UpdateCommand.java) + +The Add Command feature allows users to update an `Expenditure`, `Credit Card`, or `Income` using a single command. +This provides speed and ease of use by only requiring a single line of input, when making amendments to an entry in their list. +- Update an expenditure `/e`. +- Update a credit card `/cc`. +- Update an income `/i`. + +![update_command_sequence_diagram](images/UpdateCommandSequenceDiagram.png) +
Fig 20 - Update Command Sequence Diagram + +The sequence diagram above shows the interactions when an `UpdateCommand` is executed. +1. After receiving the `UpdateCommand` object from `Parser`, `MMM` calls the `UpdateCommand.executeCommand()` method. +2. If the expenses flag `/e` is present, it calls the `updateExpenditure()` method. +3. Else if the credit card flag `/cc` is present, it calls the `updateCreditCard()` method. +4. Else if the income flag `/i` is present, it calls the `updateIncome()` method. +5. Else, it throws an error, which is then handled by printing an error message to the user. + +
+ + +#### Update Expenditure `/e` +Updates an `Expenditure` specified by the user using the `Expenditure`'s index. The `Expenditure` is updated through the `UpdateCommand.updateExpenditure()` +method, invoked when using the `/e` flag. + +![update_expenditure_sequence_diagram](images/UpdateExpenditureSequenceDiagramFinal.png) +
Fig 21 - Update Expenditure Sequence Diagram + +The sequence diagram above shows the interactions when updating an `Expenditure`. +1. During the execution, `UpdateCommand.updateExpenditure()` will parse through user input to obtain the `PAYMENT_METHOD`, `CATEGORY`, `DESCRIPTION`, `AMOUNT` and `TIME` fields. +2. Once all the fields are obtained, `UpdateCommand.updateExpenditure()` will run tests for its respective fields. +3. The `UpdateCommand.updateExpenditure()` object formats the `CATEGORY` and `AMOUNT` fields, and the `PAYMENT_METHOD` if it is in `CASH`. +4. `UpdateCommand.updateExpenditure()` checks if the updated `Expenditure` is identical to the old `Expenditure` it is replacing, and throws an error if it is. +5. If the old `Expenditure`'s `PAYMENT_METHOD` or new `Expenditure`'s `PAYMENT_METHOD` is not in `CASH`, `UpdateCommand.updateExpenditure()` invokes `UpdateCommand.updatePaymentMethod()` to update the corresponding `creditCard.totalExpenditure`. +6. The `UpdateCommand.updateExpenditure()` object instantiates a new `Expenditure` object with the 5 fields and sets it into the `ExpenditureList`. +7. Details of the updated `Expenditure` is printed out. +8. Control is returned to `MMM`. + +
+ +#### Update Credit Card `/cc` +Updates a `Credit Card` specified by the user using the `Credit Card`'s index. The `Credit Card` is updated through the `UpdateCommand.updateCreditCard()` +method, invoked when using the `/cc` flag. + +![Update_credit_card_sequence_diagram](images/UpdateCreditCardSequenceDiagramFinal.png) +
Fig 22 - Update Credit Card Sequence Diagram + +The sequence diagram above shows the interactions when updating a `Credit Card`. +1. During the execution, `UpdateCommand.updateCreditCard()` will parse through user input to obtain the `CREDIT_CARD_NAME`, `CATEGORY`, `CASH_BACK` and `CARD_LIMIT` fields. +2. Once all the fields are obtained, `UpdateCommand.updateCreditCard()` will run checks for its respective fields. +3. The `UpdateCommand.updateCreditCard()` object instantiates a new `Credit Card` object with the aforementioned 3 fields and sets them into the `CreditCardList`, at the specified index. +5. Details of the updated `Credit Card` is printed out. +6. Control is returned to `MMM`. + +
+ +#### Update Income `/i` +Updates an `Income` specified by the user using the `Income`'s index. The `Income` is updated through the `UpdateCommand.updateIncome()` +method, invoked when using the `/i` flag. + +![Update_income_sequence_diagram](images/UpdateIncomeSequenceDiagramFinal.png) +
Fig 23 - Update Income Sequence Diagram + +The sequence diagram above shows the interactions when updating an `Income`. +1. After receiving the AddCommand object from Parser, MMM calls the `UpdateCommand.executeCommand()` method. +2. `UpdateCommand.updateIncome()` method is invoked as the `/i` flag is present. It parses through the user’s input to obtain `AMOUNT` and `CATEGORY` fields. It also runs tests on these fields to ensure the inputs are valid. +3. An `Income` object is instantiated using the aforementioned fields and is added into the `IncomeList`. +4. Details of the updated `Income` is then printed out. +5. Control is returned to `MMM`. + +
+ +#### Update Command design considerations +Aspect: To allow updating of a similar object from User's list. +* Alternative 1 (current choice): Throws an exception and warns the user that the updated `Expenditure`, `Credit Card`, or `Income` is similar to the object they are replacing. + * Pros: Users can easily verify that they have made a change in the updated object. + * Cons: Implementing this requires extraneous checks and increased coupling. + +* Alternative 2: Do not warn the user that the old object being replaced is similar to the new object. + * Pros: Easily implemented. + * Cons: Users may not be aware that the new object has no difference from the old object. + +
+ +### Storage + +Loads the user's saved information upon startup, and saves the information after every successful +command execution. + +
+ +#### Loading + +Loads the user's saved information upon startup. + +![Loading_sequence_diagram](images/LoadingSequenceDiagramFinal.png) +
+Fig 24 - Loading Sequence Diagram + +The sequence diagram above shows the interactions when loading user data. + +1. On startup, `MMM` creates a `Storage` object that loads and saves data to `data.txt`. The `Storage` object +creates the file if it does not exist. +2. `MMM` then calls `Storage.load()`, which initializes a `Scanner` that reads the data file. +3. `Storage` invokes `User.deserializeFrom()`, which reads a serialized User over the `Scanner`. +4. `User` calls `ExpenditureList.deserializeFrom()`, which returns an `ExpenditureList` read from the `Scanner`. +5. `User` then does the same for `CreditCardList.deserializeFrom()` and `IncomeList.deserializeFrom()`, which +return a `CreditCardList` and `IncomeList`, respectively. + +![Deserialize_list_sequence_diagram](images/DeserializeListSequenceDiagramFinal.png) +
+ +Fig 25 - ExpenditureList Deserialization Sequence Diagram + +The above sequence diagram shows the interactions when a list of +`MMMSerializable`s is being deserialized. Although the given diagram shows the interaction for an +`ExpenditureList`, the interactions for `CreditCardList` and `IncomeList` are similar. + +1. `ExpenditureList` calls `SerializerFunctions.convertInputToList()`, which accepts a function +that deserializes a line of input. Here, `Expenditure.deserialize()` is passed to `convertInputToList()`. +2. For each line in the input which corresponds to an `Expenditure`, `SerializerFunctions` +invokes `Expenditure.deserialize()` on this line, which returns an `Expenditure`. This is repeated until +the designated terminator is read from the `Scanner`. +3. `Expenditure` invokes `PropertyList.deserialize()` to convert the input line into a set of key-value pairs. +Then, it makes a series of `PropertyList.getValue()` calls to obtain the values of each individual property. +4. Once all attributes have been processed, `Expenditure.deserialize()` returns an `Expenditure`. +5. These expenditures are aggregated into an `ExpenditureList`, which is returned +to the `deserializeFrom()` call. + +#### Saving + +Saves user information after every successful +command execution. + +![Saving_sequence_diagram](images/SavingSequenceDiagramFinal.png) +
+ +Fig 26 - Saving Sequence Diagram + +The sequence diagram above shows the interactions when saving user data. + +1. After every command, `MMM` invokes `Storage.save()`. +2. `Storage` invokes `User.serialize()`. +3. `User` calls `ExpenditureList.serialize()`, which returns a `String`, representing the serialized +`ExpenditureList`. +4. `User` then does the same for `CreditCardList.deserializeFrom()` and `IncomeList.deserializeFrom()`, which + return `String`s representing a serialized `CreditCardList` and `IncomeList`, respectively +5. `User` compiles all these into one `String`, and returns this to `Storage`. +6. `Storage` writes the returned serialized `User` into the data file. + +![Serialize_list_sequence_diagram](images/SerializeListSequenceDiagramFinal.png) +
+ +Fig 27 - ExpenditureList Serialization Sequence Diagram + +The above sequence diagram shows the interactions when a list of +`MMMSerializable` is being serialized. Although the given diagram shows the interaction for an +`ExpenditureList`, the interactions for `CreditCardList` and `IncomeList` are similar. + +1. `ExpenditureList` calls `SerializerFunctions.addListToStringBuilder()`, passing in an `ArrayList` of +`Expenditure`s. +2. For each entry in the list, `SerializerFunctions` + invokes `Expenditure.propetyList()` on this line, which returns an `Expenditure`. This is repeated until + the designated terminator is read from the `Scanner`. +3. `Expenditure` creates a `PropertyList` to stores its properties into. + Then, it makes a series of `PropertyList.setValue()` calls to save each of its attributes. +4. Once all attributes have been processed, `PropertyList.serialize()` is called, and +this serialized `PropertyList` is returned by `Expenditure.serialize()`. +5. These serialized expenditures are aggregated into an `String`, which is returned + to the `deserializeFrom()` call. + +#### Storage Design Considerations + +Aspect: When to save user data. +* Alternative 1 (current choice): After every command. + * Pros: User state is still saved when program exits in ways other than a `ByeCommand`. + * Cons: Saving after every command may degrade performance. +* Alternative 2: At program exit (i.e. execution of `ByeCommand`). + * Pros: Less overhead per command, due to not having to save. + * Cons: User state is not saved when program exits otherwise (e.g. due to a crash). + +Aspect: What format to use in saving user data. +* Alternative 1 (current choice): Custom key-value pair format. + * Pros: Easy to read and modify by hand. + * Cons: More involved implementation. +* Alternative 2: Java's default `java.io.Serializable` interface. + * Pros: Is simple to implement. + * Cons: Is non-human-readable, which violates the requirements of this project. +* Alternative 3: JSON. + * Pros: Easy-to-read, widely established standard. + * Cons: Requires external libraries. +* Alternative 4: Incremental list of changes to user data. + * Pros: Improved performance, due to having to save less data after every command. + * Cons: More difficult to modify by hand. + +
+ +## Appendix Requirements + +### Product scope + +**Target user profile** +- a student beginner who wants to start managing their finances +- possess an income +- prefer desktop application +- is proficient in CLI +- can type fast +- prefers typing to mouse interactions + +**Value proposition** +Manage finances containing multiple payment methods faster than a typical mouse/GUI driven app. + +
+ +### User Stories + +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|-------------------------------------------------------------------------|----------------------------------------------------------------------------| +| v1.0 | new user | have a 'help' command that lists the functions of the program | view implemented features of the program | +| v1.0 | user | create an entry for expenditure | - | +| v1.0 | user | list existing entries | view my expenses | +| v1.0 | user | update existing entries | edit details of entries I have previously deleted | +| v1.0 | user | remove existing entries | delete wrongly added entries | +| v1.0 | user | know if my command entered is invalid | not expect a successful operation with bad inputs | +| v1.0 | user | exit the application | - | +| v1.0 | user | see a welcome page after running the application | have a better user experience and know the application loaded successfully | +| v2.0 | user | add a date to expenditure | track my expenditure daily, monthly or yearly | +| v2.0 | user | add my credit cards, with the credit card limits, cashback | track my expenditure on my credit card | +| v2.0 | user | allocate my expenditure into categories | know which main categories I spent the most on | +| v2.0 | user | see a graph of my expenditures for each category | know the breakdown of my expenditures | +| v2.0 | user | add my income | see if I am spending beyond my income | +| v2.0 | user | list my income, credit card and expenditure separately | better visualise the separate lists instead of seeing all at once | +| v2.0 | user | see the help page for my income, credit card and expenditure separately | refer to the the specific help page that I need | +| v2.1 | user | save and load my data | I am able to close and re run the application without losing data | + + +### Non Functional Requirements + +1. Should work on any mainstream OS as long as it has Java 11 or above installed. +3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) + should be able to accomplish most of the tasks faster using commands than using the mouse. + + +### Glossary + +* Mainstream OS: Windows, Linux, Unix, OS-X ## 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} +Given below are instructions to test the app manually. + +> **💡 Note:** +>- These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing. + +### Launch and shutdown + +1. Initial launch + 1. Download the jar file, and copy it into an empty folder. + 2. Open a terminal window whose current directory is that folder, and run `java -jar MindMyMoney.jar`.
+ Expected: Outputs a splash screen, with a daily finance tip. +2. Shutdown + 1. Enter `bye` to exit the software.
+ Expected: A farewell message is printed, and the software exits gracefully. + +### Adding data + +1. Adding expenditure paid with cash + 1. Test case: `add /e /pm Cash /c Food /d Chicken rice /a 4.50 /t 30/03/2022`
+ Expected: Expenditure added to account. Message displaying summary of new + expenditure is displayed. + 2. Test case: Missing flag, or reordering of `/pm`, `/c`, `/d`, `/a`, `/t`
+ Expected: Add command is rejected, with an appropriate error message. + 3. Test case: Invalid amount or date (date in incorrect format, future date, non-positive amount, etc.)
+ Expected: Add command is rejected, with an appropriate error message. + 4. Test case: Other valid category (Transport, Utilities, Personal, Entertainment, Others)
+ (Note: Category is case-insensitive) + Expected: Expenditure added to account. Message displaying summary of new + expenditure is displayed. + 5. Test case: Invalid category
+ Expected: Add command is rejected, with an appropriate error message. +2. Adding credit card + 1. Test case: `add /cc /n /cc DBS /cb 1.0 /cl 10000`
+ Expected: Credit card added to account. Message displaying summary of new + credit card is displayed. + 2. Test case: Cashback not in range [0, 100]
+ Expected: Add command is rejected, with an appropriate error message. + 3. Test case: Credit card limit is invalid
+ Expected: Add command is rejected, with an appropriate error message. + 4. Test case: Name is the same as an already existing credit card
+ Expected: Add command is rejected, with an appropriate error message. +3. Adding income source + 1. Test case: `add /i /a 200 /c Allowance`
+ Expected: Income source added to account. Message displaying summary of new + income source is displayed. + 2. Test case: Other valid category (Salary, Investment, Others)
+ Expected: Income source added to account. Message displaying summary of new + income source is displayed. + 3. Test case: Invalid category
+ Expected: Add command is rejected, with an appropriate error message. + 4. Test case: Invalid amount (not a positive integer)
+ Expected: Add command is rejected, with an appropriate error message. +4. Adding expenditure paid for with credit card + 1. Prerequisite: A credit card named `DBS` has been added, with a credit limit of at least 10. + 2. Test case: `add /e /pm DBS /c Personal /d Gift to friend /a 10.00 /t 01/01/2022`
+ Expected: Expenditure added to account. Message displaying summary of new + expenditure, along with updated balance of credit card, is displayed. + 3. Test case: Non-existent credit card
+ Expected: Add command is rejected, with an appropriate error message. + 4. Test case: Amount exceeding credit card's limit
+ Expected: Add command is rejected, with an appropriate error message. + +### Updating data + +1. Updating expenditures + 1. Prerequisite: There is at least 1 expenditure. + 2. Test case: `update /e 1 /pm Cash /c Entertainment /d PS5 /a 899.00 /t 25/03/2022`
+ Expected: First expenditure is updated accordingly, with a message summarizing + the new data. +2. Updating credit cards + 1. Prerequisite: There are at least 4 credit cards. + 2. Test case: `update /cc 4 /n OCBC /cb 2.00 /cl 30000`
+ Expected: Fourth credit card is updated accordingly, with a message summarizing + the new data. +3. Updating income sources + 1. Prerequisite: There are at least 2 income sources. + 2. Test case: `update /i 2 /a 500 /c Investment`
+ Expected: Second income source is updated accordingly, with a message summarizing + the new data. +4. Update edge cases + 1. Test case: Invalid index into list (not integer, negative, out of bounds, etc)
+ Expected: Update command is rejected, with an appropriate message. + 2. Note that all input format edge cases from `add` also apply here. + +### Deleting data + +1. Deleting expenditures + 1. Prerequisite: There are at least 2 expenditures. + 2. Test case: `delete /e 2`
+ Expected: Second expenditure is deleted, with a message stating its description and amount. +2. Deleting credit cards + 1. Prerequisite: There are at least 3 credit cards. + 2. Test case: `delete /cc 3`
+ Expected: Third credit card is deleted, with a message stating its name. +3. Deleting income sources + 1. Prerequisite: There is at least 1 income source. + 2. Test case: `delete /i 1`
+ Expected: First income source is deleted, with a message stating its category. +4. Delete edge cases + 1. Test case: Invalid index into list (not integer, negative, out of bounds, etc.)
+ Expected: Delete command is rejected, with an appropriate message. + +### Displaying summary information + +1. Listing information + 1. Prerequisite for ii, iii, iv: Some expenditures, credit cards, and income sources have already been `add`ed. + 2. Test case: `list /cc`
+ Expected: A list of credit cards is shown. For each credit card, its name, + cashback percentage, cashback gained, card limit, and current remaining balance + is displayed. + 3. Test case: `list /i`
+ Expected: A list of income sources is shown. For each income source, its + amount and category is displayed. + 4. Test case: `list /e`
+ Expected: A list of expenditures is displayed. For each expenditure, its + amount, category, description, and date is displayed, as well as how it was + paid for. If {DATE} is specified, a list of expenditures with the {DATE} is displayed. + 5. Prerequisite for vi: There are some expenditures whose date is in March 2022, and others which are not. + 6. Test case: `list /e 03/2022`
+ Expected: Only the expenses occurring in March 2022 are listed. + 7. Test case: `list` when the respective data source (expenditures, credit cards, income sources) is empty. + Expected: An error message telling the user to add data is displayed. +2. Calculating summary expenses + 1. Prerequisite: Some expenses have been added, under appropriate categories, all occurring sometime in March 2022. + 2. Test case: `calculate /epm 03/2022`
+ Expected: A breakdown of expenses in March 2022 per category is displayed. + +### Saving data + +1. Normal operations + 1. Prerequisites: Some data has already been added into the system. + 2. Test case: There is no `data.txt` file on startup.
+ Expected: A new file is created, and operation continues as normal. + 3. Test case: Perform `list /e`, `list /cc`, and `list /i` to view the data in the system.
+ Restart the software, and perform the list commands again. + Expected: The two sets of list commands will have the same output. +2. Manual editing + 1. Prerequisites: A save file has been created, with relevant data. + 2. Test case: A value in the save file is manually edited
+ Expected: Upon restarting the program, the new value is correctly reflected. + 3. Test case: The save file is corrupted (e.g. missing/misspelled parameters, improper + begin block/end block, missing quotes, improper values for parameters)
+ Expected: An error message detailing the parse error is displayed, and + the software proceeds to work as if the save file started as empty. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..738663b064 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,31 @@ -# Duke +# MindMyMoney -{Give product intro here} +MindMyMoney (M3) was proposed for users with multiple payment methods who wish to track their expenditures. Conducting +anecdotal interviews, we realized users face difficulty tracking their expenditures across different payment platforms +on vastly different items. MindMyMoney was then conceptualized on the basis of tracking user’s expenditures.
+ +The technical goal for the MindMyMoney (M3) is to develop an app for managing your expenditures, optimized for use via +a Command Line Interface (CLI). MindMyMoney (M3) aims to be a one-stop shop for you to consolidate your expenditure across multiple +platforms, set budget goals and track your spending. + + +# Quick Start + +1. Ensure that you have Java 11 or above installed. +2. Down the latest version of `MindMyMoney` from [here](https://github.com/AY2122S2-CS2113T-T10-4/tp/releases). + +If the setup is correct, you should see the following in your terminal: +```` + __ __ _ _ __ __ __ __ +| \/ (_)_ _ __| | \/ |_ _| \/ |___ _ _ ___ _ _ +| |\/| | | ' \/ _` | |\/| | || | |\/| / _ \ ' \/ -_) || | +|_| |_|_|_||_\__,_|_| |_|\_, |_| |_\___/_||_\___|\_, | + |__/ |__/ +<< Set a budget and stick to it >> + +Welcome to MindMyMoney +What can I do for you? +```` Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..9050e3faaf 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,865 @@ -# User Guide +# MindMyMoney User Guide + +## Content Page + +* [Introduction](#introduction) +* [Quick start](#quick-start) +* [Features](#features) + * [Introduction to Commands](#introduction-to-commands) + * [Expenditure](#expenditure) + * [Display help page for expenditures: `help`](#display-help-page-for-expenditures-help) + * [Add an expenditure: `add`](#add-an-expenditure-add) + * [Display expenditures: `list` ](#display-expenditures-list) + * [Modify an expenditure: `update`](#modify-an-expenditure-update) + * [Remove an expenditure: `delete`](#remove-an-expenditure-delete) + * [Calculate expenditures: `calculate`](#calculate-expenditures-calculate) + * [Credit Card](#credit-card) + * [Display help page for credit cards: `help`](#display-help-page-for-credit-cards-help) + * [Add a credit card: `add`](#add-a-credit-card-add) + * [Display credit cards: `list` ](#display-credit-cards-list) + * [Modify a credit card: `update`](#modify-a-credit-card-update) + * [Remove a credit card: `delete`](#remove-a-credit-card-delete) + * [Calculate cashback: `calculate` [coming in v3.0]](#calculate-cashback-calculate-coming-in-v30) + * [Income](#income) + * [Display help page for incomes: `help`](#display-help-page-for-incomes-help) + * [Add an income: `add`](#add-an-income-add) + * [Display incomes: `list`](#display-incomes-list) + * [Modify an income: `update`](#modify-an-income-update) + * [Remove an income: `delete`](#remove-an-income-delete) + * [Exit MindMyMoney application: `bye`](#exit-mindmymoney-application-bye) + * [Save the data](#save-the-data) + * [Editing the Save File](#editing-the-save-file) +* [FAQ](#faq) +* [Command summary (Expenditure)](#command-summary-expenditure) +* [Command summary (Credit Card)](#command-summary-credit-card) +* [Command summary (Income)](#command-summary-income) ## Introduction -{Give a product intro} +### MindMyMoney + +`MindMyMoney` (M3) is a desktop application for managing your personal finances, optimized for use via a +Command Line Interface (CLI). You can use it to track your expenditures across multiple payment methods, calculate +monthly expenditure, and track your income. If you are a student looking to manage your personal finances, this +application is for you! + +### Using the User Guide + +This guide aims to equip you with the knowledge on how to set up the application and to utilise its many features. Click +on the hyperlinks in the [Content Page](#content-page) to quickly navigate the user guide! Along the guide you may +encounter several icons. These icons will provide you with several useful information. +> **💡 Note:** +>- This tells you that there is additional information that is useful when you are using the application. + +> **⚠️Warning⚠️** +>- This tells you that there is some **important** information you should take note of to prevent issues from arising when you are using the application. + + +
## Quick Start -{Give steps to get started quickly} +1. Ensure that you have Java 11 or above installed. Click + [here](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) for the link to the Java 11 + installer. +2. Download the latest version of `MindMyMoney.jar` from [here](https://github.com/AY2122S2-CS2113T-T10-4/tp/releases). +3. Copy the file to the folder you want to use as the _home folder_ for your MindMyMoney. +4. Open a command line terminal in your _home folder_ and run `java -jar MindMyMoney.jar`. The startup interface similar + to the one below should appear in a few seconds. + +```` + __ __ _ _ __ __ __ __ +| \/ (_)_ _ __| | \/ |_ _| \/ |___ _ _ ___ _ _ +| |\/| | | ' \/ _` | |\/| | || | |\/| / _ \ ' \/ -_) || | +|_| |_|_|_||_\__,_|_| |_|\_, |_| |_\___/_||_\___|\_, | + |__/ |__/ +<< Set a budget and stick to it >> + +Welcome to MindMyMoney +What can I do for you? +```` + +5. Type the command in the command box and press Enter to execute it. For example: typing **`help`** and pressing Enter + will show a help page.
Some example commands you can try:
+ * **`add`**`/e /pm cash /c Food /d Porridge /a 3 /t 12/03/2022` : + Adds a $3 expenditure of the description 'Porridge' that was paid in cash on 12 March 2022 to your list of + expenditures. + * **`list /e`** : Lists all expenditures. + * **`calculate`**`/epm 03/2022` : Calculates the total expenditure in the month of March 2022. + * **`update`**`/e 1 /pm cash /c Food /d Chicken Rice /a 4.50 /t 12/03/2022` : + Updates the first expenditure on your expenditure list to reflect a $4.50 expenditure of the description 'Chicken + Rice' that was paid in cash on 12 March 2022. + * **`delete`**`/e 1` : Deletes the first expenditure in your expenditure list. + * **`bye`** : Exits the application. + +6. Refer to the [Features](#features) for more details of each command. + +
+ +# Features + +The following are features of the `MindMyMoney` application. + +## Introduction to Commands + +A `MindMyMoney` command typically contains 4 types of terms. + +- The first term in the command is known as the instruction. +- Terms in `[SQUARE_BRACKETS]` are compulsory parameters. +- Terms in `{CURLY_BRACKETS}` are optional parameters. +- Terms starting with a `/` are flags. + +For Example: + +- `list /e {DATE}` + - `list` is an instruction, `/e` is a flag and `{DATE}` is an optional parameter. +- `calculate /epm [DATE]` + - `calculate` is an instruction, `/epm` is a flag and `[DATE]` is a compulsory parameter. + +> **💡 Note:** +>- Parameters and flags are space-separated. For example: `list/e` is not a valid command while `list /e` is valid. + +> **⚠️Warning⚠️** +>- Input the parameters in the order shown in the guide, else the application will not be able to read your + > input. +>- Only use ASCII characters when entering commands into the application. Otherwise, your input may not be properly displayed in the command line. + +
+ +## Expenditure + +Expenditure refers to the various expenses you make. + +### Display help page for expenditures: `help` + +Prints a list of commands related to expenditure. + +#### Format: `help /e` + +#### Expected Outcome: + +For example: `help /e`
+Shows the help page for expenditure related commands. + +```` +> help /e +---------------------------------------Expenditure Help Page--------------------------------------- +1. Listing all Expenditures: list /e {DATE} +2. Adding an Expenditure entry: add /e /pm [PAYMENT_METHOD] /c [CATEGORY] /d [DESCRIPTION] /a [AMOUNT] /t [DATE] +3. Calculating the total expenditure in a month: calculate /epm [DATE] +4. Updating an Expenditure: update /e [NEW_INDEX] /pm [NEW_PAYMENT_METHOD] /c [NEW_CATEGORY] /d [NEW_DESCRIPTION] /a [NEW_AMOUNT] /t [NEW_DATE] +5. Removing an Expenditure entry: delete /e [INDEX] +6. Exiting the program: bye +--------------------------------------------------------------------------------------------------- +```` + +
+ +### Add an expenditure: `add` + +Adds an expenditure into your expenditure list. Only **one** expenditure can be added per command. + +#### Format: `add /e /pm [PAYMENT_METHOD] /c [CATEGORY] /d [DESCRIPTION] /a [AMOUNT] /t [DATE]` + +* `[PAYMENT_METHOD]` refers to the method of payment used. + * Enter `cash` or the name of a credit card you have saved. +* `[CATEGORY]` refers to the supported categories of expenditure. + * Enter `Food`, `Transport`, `Utilities`, `Personal`, `Entertainment` or `Others`. +* `[DESCRIPTION]` refers to the description of the expenditure. + * For example: `Porridge`. +* `[AMOUNT]` refers to the cost of the expenditure. + * Enter the amount in dollars, rounded off to the nearest cent. + * For example: an item that cost $420.69 will be entered as `420.69`. +* `[DATE]` refers to the date of the purchase of the expenditure. + * Format of the date is `dd/mm/yyyy`. + * For example: `12 March 2022` will be entered as `12/03/2022`. + +#### Expected Outcome: + +For example: `add /e /pm cash /c Food /d Porridge /a 4.50 /t 12/03/2022`
+Adds a $4.50 expenditure of food item 'Porridge' that was paid in cash on 12 March 2022 to your expenditure list. + +```` +> add /e /pm cash /c Food /d Porridge /a 4.50 /t 12/03/2022 +Successfully added: + +Description: Porridge +Amount: $4.50 +Category: Food +Payment method: Cash +Date: 12/03/2022 + +into the account +```` + +> **💡 Note:** +>- `[CATEGORY]` and `[PAYMENT_METHOD]` are **case-insensitive**. +>- Your credit card has to be [added](#add-a-credit-card-add) first before entering the name of the credit card as `[PAYMENT_METHOD]`. +>- `[AMOUNT]` only accepts numbers with 2 decimal places. Any additional decimals will be rounded off or ignored. +>- Maximum `[AMOUNT]` allowed for user is 1000000 ($1 million). + + +> **⚠️Warning⚠️** +>- `[CATEGORY]`: Any input that is not `Food`, `Transport`, `Utilities`, `Personal`, `Entertainment` or `Others` will be rejected. +>- `[DATE]` not in the format of `dd/mm/yyyy` will be rejected. +>- Input dates later than the current date will be rejected. +>- Illogical input dates will be rejected. + +
+ +### Display expenditures: `list` + +Prints your current list of expenditures. + +#### Format: `list /e {DATE}` + +* `{DATE}` refers to the date of the expenditures you would like to view. + * Enter the `{DATE}` in `dd/mm/yyyy`, `mm/yyyy` or `yyyy` format. + +#### Expected Outcome: + +For example: `list /e`
+Lists all your expenditures. + +``` +> list /e +----------------------------------------------- +1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022] +2. $20.00 was spent on Grab(Transport) using Cash [30/03/2022] +3. $3.21 was spent on Bubble Tea(Food) using Cash [30/01/1999] +4. $4.50 was spent on Porridge(Food) using Cash [12/03/2022] +----------------------------------------------- +``` + +For example: `list /e 03/2022`
+Lists all your expenditures in March 2022. + +``` +> list /e 03/2022 +----------------------------------------------- +1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022] +2. $20.00 was spent on Grab(Transport) using Cash [30/03/2022] +3. $4.50 was spent on Porridge(Food) using Cash [12/03/2022] +----------------------------------------------- +``` + +For example: `list /e 30/03/2022`
+Lists all your expenditures in 30 March 2022. + +``` +> list /e 30/03/2022 +----------------------------------------------- +1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022] +2. $20.00 was spent on Grab(Transport) using Cash [30/03/2022] +----------------------------------------------- +``` + +> **💡 Note:** +>- `list /e` will show you all expenditures stored in the list. + +> **⚠️Warning⚠️** +>- `{DATE}` not in the format of `dd/mm/yyyy`, `mm/yyyy` or `yyyy` will be rejected. +>- Before entering the command, an expenditure with the same input date must exist in the expenditure list. + +
+ +### Modify an expenditure: `update` + +Modifies an expenditure on your expenditure list at the specified index.
+Use the `list /e` command to view the indexes of your expenditures. + +#### Format: `update /e [INDEX] /pm [NEW_PAYMENT_METHOD] /c [NEW_CATEGORY] /d [NEW_DESCRIPTION] /a [NEW_AMOUNT] /t [NEW_DATE]` + +* `[INDEX]` refers to the index of expenditure in list in which you want to update. + * Enter `1` if you want to update the first expenditure in your list. +* `[NEW_PAYMENT_METHOD]` refers to the new method of payment used. + * Enter `cash` or the name of a credit card you have saved. +* `[NEW_CATEGORY]` refers to the new category of the expenditure. + * Enter `Food`, `Transport`, `Utilities`, `Personal`, `Entertainment` or `Others`. +* `[NEW_DESCRIPTION]` refers to the new description of the expenditure. + * For example: `Chicken rice`. +* `[NEW_AMOUNT]` refers to the updated of the expenditure. + * Enter the amount in dollars, rounded off to the nearest cent. + * For example: an item that cost $420.69 will be entered as `420.69`. +* `[NEW_DATE]` refers to the new date of the purchase of the expenditure. + * Format of the date is `dd/mm/yyyy`. + * For example: `12 March 2022` will be entered as `12/03/2022`. + +#### Expected Outcome: + +For example: `update /e 1 /pm cash /c Food /d chicken rice /a 5 /t 12/03/2022`.
+Updates the first expenditure in your list to a $5.00 expenditure on food item 'chicken rice' that was paid in cash on 12 +March 2022. + +```` +> list /e +----------------------------------------------- +1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022] +2. $20.00 was spent on Grab(Transport) using Cash [30/03/2022] +3. $3.21 was spent on Bubble Tea(Food) using Cash [30/01/1999] +4. $4.50 was spent on Porridge(Food) using Cash [12/03/2022] +----------------------------------------------- + +> update /e 4 /pm cash /c Food /d chicken rice /a 5 /t 12/03/2022 +Successfully set expenditure 4 to: +$5.00 was spent on chicken rice(Food) using Cash [12/03/2022] +```` + +> **💡 Note:** +> - This command is similar to the [add an expenditure](#add-an-expenditure-add) command. +> - Parameters that are labeled starting with NEW follow the same restrictions + in [add an expenditure](#add-an-expenditure-add). +> - For example: `[NEW_CATEGORY]` is **case-insensitive**, similar to `[CATEGORY]` +> - `[INDEX]` is based on the list generated from `list /e`, **not** the other variations of `list /e {DATE}`. +>- Only enter `[INDEX]` that exist in the expenditure list. For example: if you have 4 expenditures in your list, specify `[INDEX]` to be a number from 1 to 4. + +> **⚠️Warning⚠️** +> - `[NEW_CATEGORY]`: Any input not in the accepted list of categories will be rejected. +> - `[NEW_DATE]` not in the format of `dd/mm/yyyy` will be rejected. +> - Input dates later than the current date will be rejected. + +
+ +### Remove an expenditure: `delete` + +Deletes an expenditure from your expenditure list at the specified index. +Use the `list /e` command to view the current indexes of your expenditures. + +#### Format: `delete /e [INDEX]` + +* `[INDEX]` refers to the index of expenditure in list in which you want to delete. + +#### Expected Outcome: + +For example: `delete /e 1`
+Deletes the first expenditure on your list. + +```` +> list /e +----------------------------------------------- +1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022] +2. $20.00 was spent on Grab(Transport) using Cash [30/03/2022] +3. $3.21 was spent on Bubble Tea(Food) using Cash [30/01/1999] +4. $4.50 was spent on Porridge(Food) using Cash [12/03/2022] +----------------------------------------------- + +> delete /e 1 +I have removed Nike Shoes of $300.00 from the account +```` + +> **💡 Note:** +>- Only enter `[INDEX]` that exist in your list. For example: if you have 4 expenditures in your list, specify `[INDEX]` to be a number from 1 to 4. +>- `[INDEX]` is based on the list generated from `list /e`, **not** the other variations of `list /e {DATE}`. +>- Do not use `delete /e` when your expenditure list is empty. + +
+ +### Calculate expenditures: `calculate` + +Shows the total expenditure breakdown for a specified day, month or year in a horizontal bar chart.
+ +#### Format: `calculate /epm [DATE]` + +* `[DATE]` can be of the format `dd/mm/yyyy`, `mm/yyyy` or `yyyy`, depending on the duration you are interested in. + +#### Expected Outcome: + +For example: `calculate /epm 03/2022`
+Shows your total expenditure in March 2022 and breakdown of expenses. + +```` +> calculate /epm 03/2022 +Total expenditure in 03/2022 is $24.50. + +BREAKDOWN OF EXPENSES: +----------------------------------------------- +FOOD: $$$$$$$$ [18.37%] +TRANSPORT: $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ [81.63%] +UTILITIES: [0.0%] +PERSONAL: [0.0%] +ENTERTAINMENT: [0.0%] +OTHERS: [0.0%] +----------------------------------------------- +```` + +> **💡 Note** +> - An error will be shown if the date that you would like to calculate is not found in the expenditure list. + +
+ +## Credit Card + +Credit card refers to the various credit cards you might have. + +### Display help page for credit cards: `help` + +Prints a list of commands related to credit card. + +#### Format: `help /cc` + +#### Expected Outcome: + +For example: `help /cc`
+Shows the help page for credit card related commands. + +```` +> help /cc +---------------------------------------Credit Card Help Page--------------------------------------- +1. Listing all Credit Cards: list /cc +2. Adding a Credit Card: add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CREDIT_LIMIT] +3. Updating a Credit Card: update /cc [INDEX] /n [NEW_NAME] /cb [NEW_CASHBACK] /cl [NEW_CREDIT_LIMIT] +4. Removing a credit card: delete /cc [INDEX] +5. Exiting the program: bye +--------------------------------------------------------------------------------------------------- +```` + +
+ +### Add a credit card: `add` + +Adds a credit card into your credit card list. Only **one** credit card can be added per command. You can then use +this credit card as a payment method when [adding an expenditure](#add-an-expenditure-add).
+ +#### Format: `add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CARD_LIMIT]` + +* `[CREDIT_CARD_NAME]` refers to the name your Credit Card will be saved as. + * Use abbreviations for ease of adding expenditures to this credit card. For example: + storing `DBS Live Fresh Credit Card` as `DBS LF`. +* `[CASHBACK]` refers to the amount of cash back received when spending on the credit card. + * Enter the amount of cashback in percentage. + * For example: a credit card with `2% cashback` can be represented as `/cb 2`. +* `[CARD_LIMIT]` refers to the maximum monthly expenditure on this credit card. + * Enter the monthly maximum amount that can be spent on the credit card in dollars. + +#### Expected Outcome: + +For example:`add /cc /n dbs /cb 2 /cl 1000`
+Adds your credit card of the name 'DBS' with a cashback of 2% and a monthly spending limit of $1000. + +```` +> add /cc /n dbs /cb 2 /cl 1000 +Successfully added: -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +Credit card: dbs +Cash back: 2.00% +Card limit: $1000.00 -## Features +into the account +```` -{Give detailed description of each feature} +> **💡 Note** +>- `[CREDIT_CARD_NAME]` is **case-insensitive**. -### Adding a todo: `todo` -Adds a new item to the list of todo items. -Format: `todo n/TODO_NAME d/DEADLINE` +> **⚠️Warning⚠️** +>- `[CREDIT_CARD_NAME]` cannot be `cash`, `CASH`, or a combination of either. +> - You are not allowed to add 2 credit cards with the same in `[CREDIT_CARD_NAME]`. Instead, you can abbreviate the cards differently. +> - For example: If you have two DBS credit cards, you can enter the `[CREDIT_CARD_NAME]` of the first card to be `DBS_one` and the other as `DBS_two`. +>- `[CASHBACK]` cannot be more than 100%. +>- `[CARD_LIMIT]` cannot be more than $40,000. Generally, students should not have a monthly income of more than +> $10,000, and hence a monthly credit card limit of $40,000 calculated through [here](https://www.moneysmart.sg/credit-cards/credit-limit-singapore-ms). -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +
-Example of usage: +### Display credit cards: `list` -`todo n/Write the rest of the User Guide d/next week` +Prints your current list of credit cards. -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +#### Format: `list /cc` + +#### Expected Outcome: + +For example: `list /cc`
+Lists all your credit cards. + +```` +> list /cc +----------------------------------------------- +1. Name: dbs [Cashback: 2.00%] [Cashback gained: $0.00] [Card limit: $1000.00] [Balance left: $1000.00] +----------------------------------------------- +```` + +
+ +### Modify a credit card: `update` + +Modifies a credit card on your credit card list at the specified index.
+Use the `list /cc` command to view the current indexes of your credit cards. + +#### Format: `update /cc [INDEX] /n [NEW_NAME] /cb [NEW_CASHBACK] /cl [NEW_CARD_LIMIT]` + +* `[INDEX]` refers to the index of credit card in list in which you want to update. + * Enter `1` if you want to update the first credit card in your list. +* `[NEW_NAME]` refers to the name your Credit Card will be updated to. + * Use abbreviations for ease of adding expenditures to this credit card. For example: + storing `DBS Live Fresh Credit Card` as `DBS LF`. +* `[NEW_CASHBACK]` refers to the updated amount of cash back received when spending on the credit card. + * Enter the amount of cashback in percentage. + * For example: a credit card with `2% cashback` can be represented as `/cb 2`. +* `[NEW_CARD_LIMIT]` refers to the updated maximum monthly expenditure on this credit card. + * Enter the monthly maximum amount that can be spent on the credit card in dollars. + +#### Expected Outcome: + +For example: `update /cc 1 /n OCBC /cb 1.5 /cl 500`
+Updates the first credit card on your list to have a name of 'OCBC' with a cashback of 1.5% and a monthly spending limit +of $500. + +```` +> list /cc +----------------------------------------------- +1. Name: dbs [Cashback: 2.00%] [Cashback gained: $0.00] [Card limit: $1000.00] [Balance left: $1000.00] +----------------------------------------------- + +> update /cc 1 /n OCBC /cb 1.5 /cl 500 +Successfully set credit card 1 to: +Name: OCBC [Cashback: 1.50%] [Cashback gained: $0.00] [Card limit: $500.00] [Card balance: $500.00] +```` + +> **💡 Note:** +> - This command is similar to the [add a credit card](#add-a-credit-card-add) command. +> - Parameters that are labeled starting with NEW follow the same restrictions + in [add a credit card](#add-a-credit-card-add). +> - For example: `[NEW_NAME]` can be abbreviated like `[CREDIT_CARD_NAME]`. +> - Only enter `[INDEX]` that exist in your list. For example if you have 4 items in the credit card list, specify `[INDEX]` to be a number from 1 to 4. +> - You are **not allowed** to update the spending limit to an amount below what you have already spent using this card. +> - For Example: You have already spent $500 using the card. You will not be allowed to update the spending limit to a + > number less than $500. + +> **⚠️Warning⚠️** +>- Updating a credit card would cause its cashback earned to **reset to 0**. Similarly, its balance left will also + > be **reset to the spending limit**. + +
+ +### Remove a credit card: `delete` + +Deletes a credit card from your list at the specified index. +Use the `list /cc` command to view the current indexes of your credit cards. + +#### Format: `delete /cc [INDEX]` + +* `[INDEX]` refers to the index of credit card in your credit card list in which you want to delete. + * Enter `2` if you want to delete the second credit card in your credit card list. + +#### Expected Outcome + +For example: `delete /cc 1`
+Deletes the first credit card on your credit card list. + +```` +> list /cc +----------------------------------------------- +1. Name: OCBC [Cashback: 1.50%] [Cashback gained: $0.00] [Card limit: $500.00] [Balance left: $500.00] +----------------------------------------------- + +> delete /cc 1 +I have removed OCBC from your list of credit card(s). +```` + +> **💡 Note:** +>- Only enter `[INDEX]` that exist in your list. For example: if you have 4 credit cards in your list, specify `[INDEX]` to be a number from 1 to 4. +>- Do not use `delete /cc` when your credit card list is empty. + +
+ +### Calculate cashback: `calculate` [coming in v3.0] + +Details coming soon... + +
+ +## Income + +Income refers to the various sources of income you might have. + +### Display help page for incomes: `help` + +Prints a list of commands related to income. + +#### Format: `help /i` + +#### Expected Outcome: + +For example: `help /i`
+Shows the help page for income related commands. + +```` +> help /i +--------------------------------Income Help Page--------------------------------------- +1. Listing all Incomes: list /i +2. Adding an Income entry: add /i /a [AMOUNT] /c [CATEGORY] +3. Updating an Income entry: update /i [INDEX] /a [NEW_AMOUNT] /c [NEW_CATEGORY] +4. Removing an Income entry: delete /i [INDEX] +--------------------------------------------------------------------------------------- +```` + +
+ +### Add an income: `add` + +Adds an income into your income list. Only **one** income can be added per command. + +#### Format: `add /i /a [AMOUNT] /c [CATEGORY]` + +* `[AMOUNT]` refers to the monthly sum received, as a whole number. +* `[CATEGORY]` refers to the supported categories of income. + * Enter `Salary`, `Allowance`, `Investment` or `Others`. + +#### Expected Outcome: + +For example: `add /i /a 3000 /c salary`
+Adds an income of $3000 categorised as your Salary. + +```` +> add /i /a 3000 /c salary +Successfully added: + +Amount: $3000 +Category: Salary + +into the account +```` + +> **💡 Note:** +>- `[CATEGORY]` is **case-insensitive**. + +> **⚠️Warning⚠️** +>- `[CATEGORY]`: Any input that is not `Salary`, `Allowance`, `Investment` or `Others` will be rejected. +>- `[AMOUNT]`: Takes in whole numbers as an input. + > Round off your income to the nearest whole number when entering it into MindMyMoney. + +
+ +### Display incomes: `list` + +Prints your current list of income entries. + +#### Format: `list /i` + +#### Expected Outcome: + +For example: `list /i` +
Lists all your income entries. + +```` +> list /i +----------------------------------------------- +1. Amount: $3000 + Category: Salary +----------------------------------------------- +```` + +
+ +### Modify an income: `update` + +Modifies an income in your income list at the specified index. +Use the `list /i` command to view the current indexes of your income entries. + +#### Format: `update /i [INDEX] /a [NEW_AMOUNT] /c [NEW_CATEGORY]` + +* `[INDEX]` refers to the index of income in list in which you want to update. + * Enter `1` if you want to update the first entry in the list. +* `[NEW_AMOUNT]` refers to the updated monthly sum received, as a whole number. +* `[NEW_CATEGORY]` refers to the supported categories of income. + * Enter `Salary`, `Allowance`, `Investment` or `Others`. + +#### Expected Outcome: + +For example: `update /i 1 /a 4000 /c salary`
+Updates the first income entry on your income list to $4000 categorised as your salary. + +```` +> list /i +----------------------------------------------- +1. Amount: $3000 + Category: Salary +----------------------------------------------- + +> update /i 1 /a 4000 /c salary +Successfully set income 1 to: +Amount: $4000 +Category: Salary +```` + +> **💡 Note:** +> - This command is similar to the [add an income](#add-an-income-add) command. +> - Fields that are labeled starting with NEW follow the same restrictions in [add an income](#add-an-income-add). +> - For example: `[NEW_AMOUNT]` input has to be a whole number, similar to `[AMOUNT]`. +>- `[CATEGORY]` is **case-insensitive**. +> - Only enter `[INDEX]` that exist in your list. For example: if you have 4 incomes in your income list, specify `[INDEX]` to be a number from 1 to 4. + +> **⚠️Warning⚠️** +> - `[NEW_CATEGORY]`: Any input not in the accepted list of categories will be rejected. +
+ +### Remove an income: `delete` + +Deletes an income from your income list at the specified index. +Use the `list /i` command to view the current indexes of your income entries. + +#### Format: `delete /i [INDEX]` + +* `[INDEX]` refers to the index of income in your income list you want to delete. + * Enter `1` if you want to delete the first income in your income list. + +#### Expected Outcome: + +For example: `delete /i 1`
+Deletes the first income entry on your income list. + +```` +> list /i +----------------------------------------------- +1. Amount: $4000 + Category: Salary +----------------------------------------------- + +> delete /i 1 +I have removed Salary from your list of income(s). +```` + +> **💡 Note:** +>- Only enter `[INDEX]` that exist in your list. For example: if you have 4 incomes in your income list, specify `[INDEX]` to be a number from 1 to 4. +>- Do not use `delete /i` when your income list is empty. + +
+ +## Exit MindMyMoney application: `bye` + +Shuts down the MindMyMoney application. + +### Format: `bye` + +### Expected Outcome: + +For example: `bye` +Exits the program. + +```` +> bye +Bye, hope to see you again! +```` + +> **💡 Note** +> - Any input after the `bye` command is ignored. For example: `bye Hello World` will still exit the program. + +
+ +## Save the Data + +Your MindMyMoney data is saved in the hard disk automatically after any command that changes the data. There is no need +for you to save manually. You can view the saved contents of MindMyMoney by reading the `data.txt` file in your current +directory. + +> **💡 Note:** +>- If you wish to back up your MindMyMoney data, you can copy the `data.txt` file into the folder you want to save it in. +>- To load the backup data into MindMyMoney, copy `data.txt` from the backup folder into the folder containing MindMyMoney, replacing the existing copy of `data.txt`. + +### Editing the Save File +If you are experienced in using MindMyMoney, you may wish to directly edit the `data.txt` file. Below is a short +description of its format. + +> **⚠️Warning⚠️** +>- Be careful when modifying `data.txt` to follow the correct format, since doing so can corrupt the data in MindMyMoney. + > When in doubt, keep a backup, as stated above. If you are less experienced, you may use the `update` and `add` + > commands to edit the data. +
+ +`data.txt` must contain the following six lines, in this order: + +``` +# BEGIN EXPENDITURES +# END EXPENDITURES +# BEGIN CREDIT CARDS +# END CREDIT CARDS +# BEGIN INCOME SOURCES +# END INCOME SOURCES +``` + +Each expenditure, credit card, and income is stored in one line between their respective `BEGIN`/`END` lines. No line +should be left blank. + +Each piece of data is stored as a series of `key : value` pairs, separated by spaces. Both `key` and `value` +are enclosed in quotes. A `\"` in a key or value represents a quotation mark, while a `\\` represents a backslash. + +The following are the keys required for each type of data: + +- Expenditures contain `amount`, `description`, `paymentMethod`, `time`, and `category` keys. +- Credit cards contain `totalExpenditure`, `monthlyCardLimit`, `nameOfCard`, and `cashback` keys. +- Incomes contain `amount` and `category` fields. + +Their meanings are the same as in the `add` commands. The `totalExpenditure` key of a credit card contains the total +amount spent using that credit card. + +If a key is missing, MindMyMoney will consider the save file invalid. + +Here is an example of a valid save file: + +``` +# BEGIN EXPENDITURES + "amount": "3.0" "description": "Commute" "paymentMethod": "Cash" "time": "08/03/2022" "category": "Transport" + "amount": "1.0" "description": "Mala" "paymentMethod": "DBS" "time": "30/03/2022" "category": "Food" + "amount": "1.0" "description": "Chicken rice" "paymentMethod": "DBS" "time": "30/03/2022" "category": "Food" + "amount": "1.0" "description": "Katsudon" "paymentMethod": "Cash" "time": "07/03/2022" "category": "Food" +# END EXPENDITURES +# BEGIN CREDIT CARDS + "totalExpenditure": "2.0" "monthlyCardLimit": "10000.0" "nameOfCard": "DBS" "cashback": "1.0" + "totalExpenditure": "0.0" "monthlyCardLimit": "20000.0" "nameOfCard": "OCBC" "cashback": "4.0" +# END CREDIT CARDS +# BEGIN INCOME SOURCES + "amount": "200" "category": "Allowance" + "amount": "500" "category": "Investment" +# END INCOME SOURCES +``` + +
## FAQ -**Q**: How do I transfer my data to another computer? +**Q**: Why is my data not saved when I run MindMyMoney in different folders? + +**A**: MindMyMoney saves data in the current directory. To ensure all the data is saved properly, run MindMyMoney only +in the _home folder_. If you wish to run MindMyMoney in different folders and still contain your data, copy +the `data.txt` file found in the current directory into a new folder where you want to run MindMyMoney in. + +
+ +## Command Summary (Expenditure) + +| Command | Format, examples | +|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Help | `help /e`
Prints a list of commands related to expenditures. | +| Add | `add /e /pm [PAYMENT_METHOD] /c [CATEGORY] /d [DESCRIPTION] /a [AMOUNT] /t [DATE]`
For example: `add /e /pm cash /c Food /d Porridge /a 4.50 /t 10/03/2022`
Adds a $4.50 expenditure of Food item 'Porridge' that was paid in cash on 10 March 2022 to your expenditure list. | +| List | `list /e {DATE}`
For example: `list /e 03/03/2022`
Displays your current list of expenditures on 3 March 2022. | +| Calculate | `calculate /epm [DATE]`
For example: `calculate /epm 03/2022`
Prints a breakdown of your expenditures for Mar 2022. | +| Delete | `delete /e [INDEX]`
For example: `delete 1`
Deletes the first expenditure from your expenditure list. | +| Update | `update /e [INDEX] /pm [NEW_PAYMENT_METHOD] /c [NEW_CATEGORY] /d [NEW_DESCRIPTION] /a [NEW_AMOUNT] /t [NEW_DATE]`
For example: `update /e 1 /pm cash /c Food /d chicken rice /a 5 /t 12/03/2022`
Updates the first expenditure in your expenditure list to a $5.00 expenditure of Food item 'chicken rice' that was paid in cash on 12 March 2022. | +| Exit | `bye`
Ends the `MindMyMoney` application. | + +
+ +## Command Summary (Credit Card) -**A**: {your answer here} +| Command | Format, examples | +|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Help | `help /cc`
Prints a list of commands related to credit cards. | +| Add | `add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CARD_LIMIT]`
For example: `add /cc /n dbs /cb 2 /cl 1000`
Adds a credit card of the name 'DBS' with a cashback of 2% and a monthly spending limit of $1000. | +| List | `list /cc`
Displays your current list of credit cards. | +| Delete | `delete /cc [INDEX]`
For example: `delete /cc 1`
Deletes the first credit card from your credit card list. | +| Update | `update /cc [INDEX] /n [NEW_CARD_NAME] /cb [NEW_CASHBACK] /cl [NEW_CREDIT_LIMIT]`
For example: `update /cc 1 /n OCBC /cb 1.5 /cl 500`
Updates the first credit card on your credit card list to have a name of 'OCBC' with a cashback of 1.5% and a monthly spending limit of $500. | +| Exit | `bye`
Ends the `MindMyMoney` application. | -## Command Summary +
-{Give a 'cheat sheet' of commands here} +## Command Summary (Income) -* Add todo `todo n/TODO_NAME d/DEADLINE` +| Command | Format, examples | +|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Help | `help /i`
Prints a list of commands related to income. | +| Add | `add /i /a [AMOUNT] /c [CATEGORY]`
For example: `add /i /a 3000 /c salary`
Adds an income of $3000 categorised as your Salary. | +| List | `list /i`
Displays your current list of income entries. | +| Delete | `delete /i [INDEX]`
For example: `delete /i 1`
Deletes the first income from your income list. | +| Update | `update /i [INDEX] /a [NEW_AMOUNT] /c [NEW_SALARY]`
For example: `update /i 1 /a 4000 /c salary`
Updates the first income entry on your income list to $4000 categorised as your Salary. | +| Exit | `bye`
Ends the `MindMyMoney` application. | diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..0827e40308 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,2 @@ +theme: jekyll-theme-cayman +header: MindMyMoney diff --git a/docs/images/AddCommandSequenceDiagram.png b/docs/images/AddCommandSequenceDiagram.png new file mode 100644 index 0000000000..86da53f80e Binary files /dev/null and b/docs/images/AddCommandSequenceDiagram.png differ diff --git a/docs/images/AddCreditCardSequenceDiagram.png b/docs/images/AddCreditCardSequenceDiagram.png new file mode 100644 index 0000000000..a61e4ff978 Binary files /dev/null and b/docs/images/AddCreditCardSequenceDiagram.png differ diff --git a/docs/images/AddCreditCardSequence_Diagram.png b/docs/images/AddCreditCardSequence_Diagram.png new file mode 100644 index 0000000000..a61e4ff978 Binary files /dev/null and b/docs/images/AddCreditCardSequence_Diagram.png differ diff --git a/docs/images/AddExpenditureCommandSequenceDiagram.png b/docs/images/AddExpenditureCommandSequenceDiagram.png new file mode 100644 index 0000000000..f7cf64e311 Binary files /dev/null and b/docs/images/AddExpenditureCommandSequenceDiagram.png differ diff --git a/docs/images/AddExpenditureSequenceDiagram.png b/docs/images/AddExpenditureSequenceDiagram.png new file mode 100644 index 0000000000..f7cf64e311 Binary files /dev/null and b/docs/images/AddExpenditureSequenceDiagram.png differ diff --git a/docs/images/AddIncomeSequenceDiagram.png b/docs/images/AddIncomeSequenceDiagram.png new file mode 100644 index 0000000000..1044f83830 Binary files /dev/null and b/docs/images/AddIncomeSequenceDiagram.png differ diff --git a/docs/images/ArchitectureDiagramFinal.png b/docs/images/ArchitectureDiagramFinal.png new file mode 100644 index 0000000000..e658b8b020 Binary files /dev/null and b/docs/images/ArchitectureDiagramFinal.png differ diff --git a/docs/images/CalculateCommandSequenceDiagramFinal.png b/docs/images/CalculateCommandSequenceDiagramFinal.png new file mode 100644 index 0000000000..9f8210511c Binary files /dev/null and b/docs/images/CalculateCommandSequenceDiagramFinal.png differ diff --git a/docs/images/CommandClassDiagram.png b/docs/images/CommandClassDiagram.png new file mode 100644 index 0000000000..b6c6eb3e47 Binary files /dev/null and b/docs/images/CommandClassDiagram.png differ diff --git a/docs/images/ComponentsSequenceDiagramFinal.png b/docs/images/ComponentsSequenceDiagramFinal.png new file mode 100644 index 0000000000..04582d91b3 Binary files /dev/null and b/docs/images/ComponentsSequenceDiagramFinal.png differ diff --git a/docs/images/Dan_Profile_Picture.png b/docs/images/Dan_Profile_Picture.png new file mode 100644 index 0000000000..2aded313a7 Binary files /dev/null and b/docs/images/Dan_Profile_Picture.png differ diff --git a/docs/images/DeleteCommandSequenceDiagram.png b/docs/images/DeleteCommandSequenceDiagram.png new file mode 100644 index 0000000000..c55215848f Binary files /dev/null and b/docs/images/DeleteCommandSequenceDiagram.png differ diff --git a/docs/images/DeleteCreditCardSequenceDiagram.png b/docs/images/DeleteCreditCardSequenceDiagram.png new file mode 100644 index 0000000000..2a44a83146 Binary files /dev/null and b/docs/images/DeleteCreditCardSequenceDiagram.png differ diff --git a/docs/images/DeleteExpenditureSequenceDiagramFinal.png b/docs/images/DeleteExpenditureSequenceDiagramFinal.png new file mode 100644 index 0000000000..a52a959593 Binary files /dev/null and b/docs/images/DeleteExpenditureSequenceDiagramFinal.png differ diff --git a/docs/images/DeleteIncomeSequenceDiagram.png b/docs/images/DeleteIncomeSequenceDiagram.png new file mode 100644 index 0000000000..924446a7e3 Binary files /dev/null and b/docs/images/DeleteIncomeSequenceDiagram.png differ diff --git a/docs/images/DeleteIncomeSequenceDiagramFinal.png b/docs/images/DeleteIncomeSequenceDiagramFinal.png new file mode 100644 index 0000000000..924446a7e3 Binary files /dev/null and b/docs/images/DeleteIncomeSequenceDiagramFinal.png differ diff --git a/docs/images/DeserializeListSequenceDiagramFinal.png b/docs/images/DeserializeListSequenceDiagramFinal.png new file mode 100644 index 0000000000..d94c01c552 Binary files /dev/null and b/docs/images/DeserializeListSequenceDiagramFinal.png differ diff --git a/docs/images/Glendon_Profile_Picture.png b/docs/images/Glendon_Profile_Picture.png new file mode 100644 index 0000000000..e1acfac1d9 Binary files /dev/null and b/docs/images/Glendon_Profile_Picture.png differ diff --git a/docs/images/KitHan_Profile_Picture.png b/docs/images/KitHan_Profile_Picture.png new file mode 100644 index 0000000000..4a33c52dff Binary files /dev/null and b/docs/images/KitHan_Profile_Picture.png differ diff --git a/docs/images/LimJieRui_Profile_Picture(Final).png b/docs/images/LimJieRui_Profile_Picture(Final).png new file mode 100644 index 0000000000..f01cccff63 Binary files /dev/null and b/docs/images/LimJieRui_Profile_Picture(Final).png differ diff --git a/docs/images/ListCommandSequenceDiagram.png b/docs/images/ListCommandSequenceDiagram.png new file mode 100644 index 0000000000..05ebcea205 Binary files /dev/null and b/docs/images/ListCommandSequenceDiagram.png differ diff --git a/docs/images/ListCreditCardSequenceDiagram.png b/docs/images/ListCreditCardSequenceDiagram.png new file mode 100644 index 0000000000..fdb88f2190 Binary files /dev/null and b/docs/images/ListCreditCardSequenceDiagram.png differ diff --git a/docs/images/ListExpenditureSequenceDiagram.png b/docs/images/ListExpenditureSequenceDiagram.png new file mode 100644 index 0000000000..a1297e24ce Binary files /dev/null and b/docs/images/ListExpenditureSequenceDiagram.png differ diff --git a/docs/images/ListIncomeSequenceDiagram.png b/docs/images/ListIncomeSequenceDiagram.png new file mode 100644 index 0000000000..84b2c11c1a Binary files /dev/null and b/docs/images/ListIncomeSequenceDiagram.png differ diff --git a/docs/images/LoadingSequenceDiagramFinal.png b/docs/images/LoadingSequenceDiagramFinal.png new file mode 100644 index 0000000000..f169065a83 Binary files /dev/null and b/docs/images/LoadingSequenceDiagramFinal.png differ diff --git a/docs/images/ParserClassDiagram.png b/docs/images/ParserClassDiagram.png new file mode 100644 index 0000000000..c39ffec5aa Binary files /dev/null and b/docs/images/ParserClassDiagram.png differ diff --git a/docs/images/SavingSequenceDiagramFinal.png b/docs/images/SavingSequenceDiagramFinal.png new file mode 100644 index 0000000000..f8a8c10720 Binary files /dev/null and b/docs/images/SavingSequenceDiagramFinal.png differ diff --git a/docs/images/Sean_Profile_Picture.png b/docs/images/Sean_Profile_Picture.png new file mode 100644 index 0000000000..543e7758e3 Binary files /dev/null and b/docs/images/Sean_Profile_Picture.png differ diff --git a/docs/images/SerializeListSequenceDiagramFinal.png b/docs/images/SerializeListSequenceDiagramFinal.png new file mode 100644 index 0000000000..50e564e16e Binary files /dev/null and b/docs/images/SerializeListSequenceDiagramFinal.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png new file mode 100644 index 0000000000..822cad5238 Binary files /dev/null and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png new file mode 100644 index 0000000000..478a83beb6 Binary files /dev/null and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UpdateCommandSequenceDiagram.png b/docs/images/UpdateCommandSequenceDiagram.png new file mode 100644 index 0000000000..b5de07c723 Binary files /dev/null and b/docs/images/UpdateCommandSequenceDiagram.png differ diff --git a/docs/images/UpdateCreditCardSequenceDiagramFinal.png b/docs/images/UpdateCreditCardSequenceDiagramFinal.png new file mode 100644 index 0000000000..7092eef4bf Binary files /dev/null and b/docs/images/UpdateCreditCardSequenceDiagramFinal.png differ diff --git a/docs/images/UpdateExpenditureSequenceDiagramFinal.png b/docs/images/UpdateExpenditureSequenceDiagramFinal.png new file mode 100644 index 0000000000..1bdd3f86ac Binary files /dev/null and b/docs/images/UpdateExpenditureSequenceDiagramFinal.png differ diff --git a/docs/images/UpdateIncomeSequenceDiagramFinal.png b/docs/images/UpdateIncomeSequenceDiagramFinal.png new file mode 100644 index 0000000000..c6c46a84ba Binary files /dev/null and b/docs/images/UpdateIncomeSequenceDiagramFinal.png differ diff --git a/docs/puml/AddCommandSequenceDiagram.puml b/docs/puml/AddCommandSequenceDiagram.puml new file mode 100644 index 0000000000..d084010640 --- /dev/null +++ b/docs/puml/AddCommandSequenceDiagram.puml @@ -0,0 +1,58 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box AddCommand +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:AddCommand" as AddCommand COMMANDS_COLOUR +end box +hide footbox + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +MMM -> AddCommand: executeCommand() +activate AddCommand COMMANDS_COLOUR + +alt hasCreditCardFlag() +AddCommand -> AddCommand: addCreditCard() +activate AddCommand COMMANDS_COLOUR +AddCommand --> AddCommand +deactivate AddCommand +AddCommand --> MMM + + +else hasIncomeFlag() +AddCommand -> AddCommand: addIncome() +activate AddCommand COMMANDS_COLOUR +AddCommand --> AddCommand +deactivate AddCommand +AddCommand --> MMM + + +else hasExpenditureFlag() +AddCommand -> AddCommand: addExpenditure() +activate AddCommand COMMANDS_COLOUR +AddCommand --> AddCommand: +deactivate AddCommand COMMANDS_COLOUR +AddCommand --> MMM + +else else +AddCommand --> MMM +deactivate AddCommand COMMANDS_COLOUR +note right +An exception containing warning messages is thrown +end note +end +@enduml +@enduml diff --git a/docs/puml/AddCreditCardSequenceDiagram.puml b/docs/puml/AddCreditCardSequenceDiagram.puml new file mode 100644 index 0000000000..034bb72de1 --- /dev/null +++ b/docs/puml/AddCreditCardSequenceDiagram.puml @@ -0,0 +1,58 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box addCreditCard() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:AddCommand" as AddCommand COMMANDS_COLOUR +participant ":GeneralFunctions" as GeneralFunctions HELPER_COLOUR +participant ":AddCommandInputTest" as AddCommandInputTest HELPER_COLOUR +participant ":CreditCard" as CreditCard USERFINANCIAL_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR + +end box +hide footbox +MMM -> AddCommand: executeCommand() +activate AddCommand COMMANDS_COLOUR + +AddCommand -> AddCommand: addCreditCard() +activate AddCommand COMMANDS_COLOUR + +AddCommand -> GeneralFunctions: parseInputWithCommandFlag() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> AddCommand +deactivate GeneralFunctions + +AddCommand -> AddCommandInputTest: testCreditCardParameters() +activate AddCommandInputTest HELPER_COLOUR + +AddCommandInputTest --> AddCommand +deactivate AddCommandInputTest + +create CreditCard +AddCommand -> CreditCard: CreditCard() +activate CreditCard USERFINANCIAL_COLOUR +CreditCard --> AddCommand +deactivate CreditCard + +AddCommand -> CreditCardList: add() +activate CreditCardList DATA_COLOUR +CreditCardList --> AddCommand +deactivate CreditCardList + +note left +Details of newly added +Credit Card is printed out +end note + +AddCommand --> AddCommand: +deactivate AddCommand +AddCommand --> MMM: +deactivate AddCommand +@enduml diff --git a/docs/puml/AddExpenditureCommandSequenceDiagram.puml b/docs/puml/AddExpenditureCommandSequenceDiagram.puml new file mode 100644 index 0000000000..5f17bed642 --- /dev/null +++ b/docs/puml/AddExpenditureCommandSequenceDiagram.puml @@ -0,0 +1,96 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box addExpenditure() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:AddCommand" as AddCommand COMMANDS_COLOUR +participant ":GeneralFunctions" as GeneralFunctions HELPER_COLOUR +participant ":AddCommandInputTest" as AddCommandInputTest HELPER_COLOUR +participant ":Expenditure" as Expenditure USERFINANCIAL_COLOUR +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR +participant ":CreditCard" as CreditCard USERFINANCIAL_COLOUR +end box +hide footbox + +MMM -> AddCommand: executeCommand() +activate AddCommand COMMANDS_COLOUR + +AddCommand -> AddCommand: addExpenditure() +activate AddCommand COMMANDS_COLOUR + +AddCommand -> GeneralFunctions: parseInputWithCommandFlag() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> AddCommand +deactivate GeneralFunctions + + +AddCommand -> AddCommandInputTest: testExpenditureParameters() +activate AddCommandInputTest HELPER_COLOUR + +AddCommandInputTest --> AddCommand +deactivate AddCommandInputTest +AddCommand --> AddCommand +deactivate AddCommand COMMANDS_COLOUR + +opt PAYMENT_METHOD == cash +AddCommand -> GeneralFunctions: capitalise(paymentMethod) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> AddCommand +deactivate GeneralFunctions +end +AddCommand -> GeneralFunctions: capitalise(category) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> AddCommand +deactivate GeneralFunctions + +AddCommand -> GeneralFunctions: formatFloat(amount) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> AddCommand +deactivate GeneralFunctions + +create Expenditure +AddCommand -> Expenditure: Expenditure() +activate Expenditure USERFINANCIAL_COLOUR +Expenditure --> AddCommand +deactivate Expenditure + +AddCommand -> ExpenditureList: add() +activate ExpenditureList DATA_COLOUR +ExpenditureList --> AddCommand +deactivate ExpenditureList + +note left +Details of newly added expenditure +is printed out +end note + +opt PAYMENT_METHOD != cash + +AddCommand -> AddCommand: updateCreditCardTotalExpenditure() +activate AddCommand COMMANDS_COLOUR +AddCommand -> CreditCardList: get() +activate CreditCardList DATA_COLOUR +CreditCardList --> AddCommand +deactivate CreditCardList + +AddCommand -> CreditCard: addExpenditure(amount) +activate CreditCard USERFINANCIAL_COLOUR +CreditCard --> AddCommand +deactivate CreditCard + +AddCommand --> AddCommand +deactivate AddCommand + +end + +AddCommand --> MMM +deactivate AddCommand +@enduml diff --git a/docs/puml/AddIncomeSequenceDiagram.puml b/docs/puml/AddIncomeSequenceDiagram.puml new file mode 100644 index 0000000000..6d76e3cc0b --- /dev/null +++ b/docs/puml/AddIncomeSequenceDiagram.puml @@ -0,0 +1,63 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box addIncome() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:AddCommand" as C COMMANDS_COLOUR +participant ":GeneralFunctions" as GF HELPER_COLOUR +participant ":AddCommandInputTests" as IT HELPER_COLOUR +participant ":Income" as I USERFINANCIAL_COLOUR +participant ":IncomeList" as IL DATA_COLOUR +end box + +MMM -> C: executeCommand() + +activate C COMMANDS_COLOUR +C -> C: addIncome() +activate C COMMANDS_COLOUR + +C -> GF: parseInputWithCommandFlag() +activate GF HELPER_COLOUR +GF --> C +deactivate GF + +C -> IT: testIncomeParameters() +activate IT HELPER_COLOUR + +IT --> C +deactivate IT + + +create I +C -> I: Income() +activate I USERFINANCIAL_COLOUR +I --> C +deactivate + +C -> IL: add() +activate IL DATA_COLOUR +IL --> C +deactivate + +C --> C +deactivate + +note left +Details of newly added +Income is printed out +end note + +C --> MMM +deactivate + +@enduml diff --git a/docs/puml/ArchitectureDiagram.puml b/docs/puml/ArchitectureDiagram.puml new file mode 100644 index 0000000000..cb6757ee99 --- /dev/null +++ b/docs/puml/ArchitectureDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include Style.puml + +skinparam componentStyle rectangle +skinparam ArrowColor black +skinparam ActorBorderColor black + +actor User +folder data.txt +component MMM MMM_COLOUR +component Ui UI_COLOUR +component Parser PARSER_COLOUR +component Commands COMMANDS_COLOUR +component Storage STORAGE_COLOUR + +note bottom of MMM +MMM refers to +MindMyMoney +end note + +User -d-> Ui +Ui <-> MMM +MMM <-u-> Parser +MMM -r-> Commands +Parser --> Commands +MMM <-d-> Storage +Storage <-d-> data.txt + +@enduml diff --git a/docs/puml/CalculateCommandSequenceDiagram.puml b/docs/puml/CalculateCommandSequenceDiagram.puml new file mode 100644 index 0000000000..e31b63150c --- /dev/null +++ b/docs/puml/CalculateCommandSequenceDiagram.puml @@ -0,0 +1,51 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box CalculateInputCommand +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:CalculateInputCommand" as CalculateInputCommand COMMANDS_COLOUR +participant ":Calculations" as Calculations HELPER_COLOUR +participant ":GeneralFunctions" as GeneralFunctions HELPER_COLOUR +end box +hide footbox + +MMM -> CalculateInputCommand: executeCommand() +activate CalculateInputCommand COMMANDS_COLOUR + +CalculateInputCommand -> GeneralFunctions: parseInput() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> CalculateInputCommand +deactivate GeneralFunctions HELPER_COLOUR + + +alt contain FLAG_OF_EXPENDITURE_PER_MONTH +CalculateInputCommand -> Calculations: calculateExpenditure() +activate Calculations HELPER_COLOUR +Calculations -> GeneralFunctions: findItemInList() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> Calculations +deactivate GeneralFunctions HELPER_COLOUR +Calculations -> Calculations: displayCalculationBreakdown() +activate Calculations HELPER_COLOUR +Calculations --> Calculations +deactivate Calculations HELPER_COLOUR +Calculations --> CalculateInputCommand +deactivate Calculations HELPER_COLOUR +CalculateInputCommand --> MMM + +else else +CalculateInputCommand --> MMM +note right +An exception containing warning messages is thrown +end note +deactivate CalculateInputCommand COMMANDS_COLOUR +end + +@enduml \ No newline at end of file diff --git a/docs/puml/CommandClassDiagram.puml b/docs/puml/CommandClassDiagram.puml new file mode 100644 index 0000000000..2a1a7faf12 --- /dev/null +++ b/docs/puml/CommandClassDiagram.puml @@ -0,0 +1,38 @@ +@startuml +!include Style.puml + +skinparam componentStyle rectangle +skinparam packageStyle rectangle +skinparam ArrowColor black + +package Parser { +component Parser as parser PARSER_COLOUR + +package Command { +component "{abstract}\nCommand" as command COMMANDS_COLOUR +component "AddCommand" as addCommand COMMANDS_COLOUR +component "ByeCommand" as byeCommand COMMANDS_COLOUR +component "DeleteCommand" as deleteCommand COMMANDS_COLOUR +component "CalculateInputCommand" as calculateInputCommand COMMANDS_COLOUR +component "HelpCommand" as helpCommand COMMANDS_COLOUR +component "ListCommand" as listCommand COMMANDS_COLOUR +component "UpdateCommand" as updateCommand COMMANDS_COLOUR + +} + +} +component MMM MMM_COLOUR + +MMM .r.> parser + +parser ...> "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t0..1" command: "creates >" + +command <|-left- addCommand +command <|-- byeCommand +command <|-- deleteCommand +command <|-- calculateInputCommand +command <|-up- helpCommand +command <|-up- listCommand +command <|-right- updateCommand + +@enduml \ No newline at end of file diff --git a/docs/puml/ComponentsSequenceDiagram.puml b/docs/puml/ComponentsSequenceDiagram.puml new file mode 100644 index 0000000000..b6b6d8efa7 --- /dev/null +++ b/docs/puml/ComponentsSequenceDiagram.puml @@ -0,0 +1,47 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +ActorBorderColor black +} + +box Components Sequence Diagram +actor User +participant ":MMM" as MMM MMM_COLOUR +participant ":Ui" as Ui UI_COLOUR +participant ":Parser" as Parser PARSER_COLOUR +participant ":Commands" as Commands COMMANDS_COLOUR +participant ":Storage" as Storage STORAGE_COLOUR +end box + +MMM -> Ui: readInput() +activate Ui UI_COLOUR +User -> Ui: \t\t add /e /pm cash /c food /d Porridge /a 3 /t 04/04/2022 +Ui --> MMM: add /e /pm cash /c food /d Porridge /a 3 /t 04/04/2022 +deactivate Ui + +MMM -> Parser: parseCommand() +activate Parser PARSER_COLOUR +Parser --> MMM: AddCommand() +deactivate Parser + +MMM -> Commands: executeCommand() +activate Commands COMMANDS_COLOUR +Commands --> MMM +deactivate Commands + +MMM -> Storage: save() +activate Storage STORAGE_COLOUR +Storage --> MMM +deactivate Storage + +MMM --> User + +@enduml diff --git a/docs/puml/DeleteCommandSequenceDiagram.puml b/docs/puml/DeleteCommandSequenceDiagram.puml new file mode 100644 index 0000000000..7e6b9cfce5 --- /dev/null +++ b/docs/puml/DeleteCommandSequenceDiagram.puml @@ -0,0 +1,50 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box DeleteCommand +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:DeleteCommand" as C COMMANDS_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +alt hasExpensesFlag() +C -> C: deleteExpenditure() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else hasCreditCardListFlag() +C -> C: deleteCreditCard() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else hasIncomeListFlag() +C -> C: deleteIncome() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else else +C --> MMM +note right +An exception containing warning messages is thrown +end note + +end +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/DeleteCreditCardSequenceDiagram.puml b/docs/puml/DeleteCreditCardSequenceDiagram.puml new file mode 100644 index 0000000000..db752df9af --- /dev/null +++ b/docs/puml/DeleteCreditCardSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box deleteCreditCard() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:DeleteCommand" as C COMMANDS_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +C -> C: deleteCreditCard() +activate C COMMANDS_COLOUR + +C -> CreditCardList: delete() +activate CreditCardList DATA_COLOUR +CreditCardList --> C +deactivate CreditCardList + +C --> C + +note left +Details of deleted +Credit Card is printed out +end note + +deactivate C + +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/DeleteExpenditureSequenceDiagram.puml b/docs/puml/DeleteExpenditureSequenceDiagram.puml new file mode 100644 index 0000000000..4307159abe --- /dev/null +++ b/docs/puml/DeleteExpenditureSequenceDiagram.puml @@ -0,0 +1,67 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box deleteExpenditure() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:DeleteCommand" as C COMMANDS_COLOUR +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR +participant ":Expenditure" as Expenditure USERFINANCIAL_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR +participant ":CreditCard" as CreditCard USERFINANCIAL_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +C -> C: deleteExpenditure() +activate C COMMANDS_COLOUR + +C -> ExpenditureList: get() +activate ExpenditureList DATA_COLOUR +ExpenditureList --> C +deactivate ExpenditureList + +C -> Expenditure: getPaymentMethod() +activate Expenditure USERFINANCIAL_COLOUR +Expenditure --> C +deactivate Expenditure + +opt PAYMENT_METHOD != cash +C -> C: updateCreditCardTotalExpenditure() +activate C COMMANDS_COLOUR + +C -> CreditCardList: get() +activate CreditCardList DATA_COLOUR +CreditCardList --> C +deactivate CreditCardList + +C -> CreditCard: deductExpenditure() +activate CreditCard USERFINANCIAL_COLOUR +CreditCard --> C +deactivate CreditCard + +C --> C +note right +Details of deleted +expenditure is printed out +end note +deactivate C +end + +C --> C +deactivate C + +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/DeleteIncomeSequenceDiagram.puml b/docs/puml/DeleteIncomeSequenceDiagram.puml new file mode 100644 index 0000000000..880c898548 --- /dev/null +++ b/docs/puml/DeleteIncomeSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box deleteIncome() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:DeleteCommand" as C COMMANDS_COLOUR +participant ":IncomeList" as IL DATA_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +C -> C: deleteIncome() +activate C COMMANDS_COLOUR + +C -> IL: delete() +activate IL DATA_COLOUR +IL --> C +deactivate IL + +C --> C + +note right +Details of deleted +Income is printed out +end note + +deactivate C + +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/DeserializeListSequenceDiagram.puml b/docs/puml/DeserializeListSequenceDiagram.puml new file mode 100644 index 0000000000..ba180d845d --- /dev/null +++ b/docs/puml/DeserializeListSequenceDiagram.puml @@ -0,0 +1,41 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box Deserialize List +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR +participant ":SerializerFunctions" as SerializerFunctions HELPER_COLOUR +participant ":Expenditure" as Expenditure USERFINANCIAL_COLOUR +participant ":PropertyList" as PropertyList DATA_COLOUR +end box +hide footbox + +[-> ExpenditureList : deserializeFrom() +activate ExpenditureList DATA_COLOUR +ExpenditureList -> SerializerFunctions : convertInputToList() +activate SerializerFunctions HELPER_COLOUR +loop until end line is read + SerializerFunctions -> Expenditure ** : deserialize() + activate Expenditure USERFINANCIAL_COLOUR + deactivate Expenditure + + Expenditure -> PropertyList ** : deserialize() + activate PropertyList DATA_COLOUR + deactivate PropertyList + PropertyList --> Expenditure -- + Expenditure -> PropertyList: getValue() + activate PropertyList DATA_COLOUR + PropertyList --> Expenditure -- + + Expenditure --> SerializerFunctions -- +end +SerializerFunctions --> ExpenditureList -- +[<-- ExpenditureList -- + +@enduml diff --git a/docs/puml/ListCommandSequenceDiagram.puml b/docs/puml/ListCommandSequenceDiagram.puml new file mode 100644 index 0000000000..3668bb07d9 --- /dev/null +++ b/docs/puml/ListCommandSequenceDiagram.puml @@ -0,0 +1,50 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box ListCommand +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:ListCommand" as C COMMANDS_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +alt hasExpensesFlag() +C -> C: printExpenditureList() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else hasCreditCardListFlag() +C -> C: printCreditCardList() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else hasIncomeListFlag() +C -> C: printIncomeList() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else else +C --> MMM +note right +An exception containing warning messages is thrown +end note + +end +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/ListCreditCardSequenceDiagram.puml b/docs/puml/ListCreditCardSequenceDiagram.puml new file mode 100644 index 0000000000..75a6f3b424 --- /dev/null +++ b/docs/puml/ListCreditCardSequenceDiagram.puml @@ -0,0 +1,36 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box listCreditCard() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:ListCommand" as C COMMANDS_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +C -> C: printCreditCardList() +activate C COMMANDS_COLOUR + +C -> C: creditCardListToString() +activate C COMMANDS_COLOUR + +C --> C: listInString +deactivate C + +C --> C +deactivate C + +C --> MMM +deactivate C +@enduml diff --git a/docs/puml/ListExpenditureSequenceDiagram.puml b/docs/puml/ListExpenditureSequenceDiagram.puml new file mode 100644 index 0000000000..834f7fe07d --- /dev/null +++ b/docs/puml/ListExpenditureSequenceDiagram.puml @@ -0,0 +1,44 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box listExpenditure() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:ListCommand" as C COMMANDS_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +C -> C: printExpenditureList() +activate C COMMANDS_COLOUR + +alt listInput.equals("/e") +C -> C: listString() +activate C COMMANDS_COLOUR +C --> C: listInString +deactivate C + +else else +C -> C: listStringWithDate() +activate C COMMANDS_COLOUR +C --> C: listInString +deactivate C +end + +C --> C +deactivate C + +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/ListIncomeSequenceDiagram.puml b/docs/puml/ListIncomeSequenceDiagram.puml new file mode 100644 index 0000000000..d5131ff970 --- /dev/null +++ b/docs/puml/ListIncomeSequenceDiagram.puml @@ -0,0 +1,37 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box listIncome() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:ListCommand" as C COMMANDS_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +C -> C: printIncomeList() +activate C COMMANDS_COLOUR + +C -> C: incomeListToString() +activate C COMMANDS_COLOUR + +C --> C: listInString +deactivate C + +C --> C +deactivate C + +C --> MMM +deactivate C + +@enduml diff --git a/docs/puml/LoadingSequenceDiagram.puml b/docs/puml/LoadingSequenceDiagram.puml new file mode 100644 index 0000000000..19ff8f632a --- /dev/null +++ b/docs/puml/LoadingSequenceDiagram.puml @@ -0,0 +1,41 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box Loading +participant ":MMM" as MMM MMM_COLOUR +participant ":Storage" as Storage STORAGE_COLOUR +participant ":User" as User USERFINANCIAL_COLOUR +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR +participant ":IncomeList" as IncomeList DATA_COLOUR +end box +hide footbox + +MMM -> Storage ** : Storage() +activate Storage STORAGE_COLOUR +Storage --> MMM -- + +MMM -> Storage : load() +activate Storage STORAGE_COLOUR +Storage -> User : deserializeFrom() +activate User USERFINANCIAL_COLOUR +User -> ExpenditureList : deserializeFrom() +activate ExpenditureList DATA_COLOUR +ref over User, ExpenditureList : deserialize list +ExpenditureList --> User -- +User -> CreditCardList : deserializeFrom() +activate CreditCardList DATA_COLOUR +CreditCardList --> User -- +User -> IncomeList : deserializeFrom() +activate IncomeList DATA_COLOUR +IncomeList --> User -- +User --> Storage -- +Storage --> MMM -- +@enduml diff --git a/docs/puml/ParserClassDiagram.puml b/docs/puml/ParserClassDiagram.puml new file mode 100644 index 0000000000..54483204d4 --- /dev/null +++ b/docs/puml/ParserClassDiagram.puml @@ -0,0 +1,39 @@ +@startuml +!include Style.puml + +skinparam componentStyle rectangle +skinparam packageStyle rectangle +skinparam ArrowColor black + +package Parser { +component Parser as P PARSER_COLOUR + +Package User { +component User as U USERFINANCIAL_COLOUR +component ExpenditureList DATA_COLOUR +component Expenditure USERFINANCIAL_COLOUR +component CreditCardList DATA_COLOUR +component CreditCard USERFINANCIAL_COLOUR +component IncomeList DATA_COLOUR +component Income USERFINANCIAL_COLOUR +} + +} +component MMM MMM_COLOUR +component Commands COMMANDS_COLOUR +component GeneralFunctions HELPER_COLOUR + +MMM .d.> P +P .r.> GeneralFunctions: "uses >" +P .d.> Commands: "instantiates >" +P .d.> U + +U -d-> "0..1" ExpenditureList: "has >" +U -d-> "0..1" CreditCardList: "has >" +U -d-> "0..1" IncomeList: "has >" + +ExpenditureList .d.> Expenditure +CreditCardList .d.> CreditCard +IncomeList .d.> Income + +@enduml diff --git a/docs/puml/SavingSequenceDiagram.puml b/docs/puml/SavingSequenceDiagram.puml new file mode 100644 index 0000000000..345b433628 --- /dev/null +++ b/docs/puml/SavingSequenceDiagram.puml @@ -0,0 +1,40 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box Saving +participant ":MMM" as MMM MMM_COLOUR +participant ":Storage" as Storage STORAGE_COLOUR +participant ":User" as User USERFINANCIAL_COLOUR +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR +participant ":IncomeList" as IncomeList DATA_COLOUR +end box +hide footbox + +MMM -> Storage : save() +activate Storage STORAGE_COLOUR +Storage -> User : serialize() +activate User USERFINANCIAL_COLOUR +User -> ExpenditureList : serialize() +activate ExpenditureList DATA_COLOUR + +ref over User, ExpenditureList : serialize list +ExpenditureList --> User -- +User -> CreditCardList : serialize() +activate CreditCardList DATA_COLOUR + +CreditCardList --> User -- +User -> IncomeList : serialize() +activate IncomeList DATA_COLOUR + +IncomeList --> User -- +User --> Storage -- +deactivate Storage +@enduml diff --git a/docs/puml/SerializeListSequenceDiagram.puml b/docs/puml/SerializeListSequenceDiagram.puml new file mode 100644 index 0000000000..10267e515c --- /dev/null +++ b/docs/puml/SerializeListSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box Serialize List +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR +participant ":SerializerFunctions" as SerializerFunctions HELPER_COLOUR +participant ":Expenditure" as Expenditure USERFINANCIAL_COLOUR +participant ":PropertyList" as PropertyList DATA_COLOUR +end box +hide footbox + +[-> ExpenditureList : serialize() +activate ExpenditureList DATA_COLOUR +ExpenditureList -> SerializerFunctions : addListToStringBuilder() +activate SerializerFunctions HELPER_COLOUR +loop for each element in list + SerializerFunctions -> Expenditure : serialize() + activate Expenditure USERFINANCIAL_COLOUR + + Expenditure -> PropertyList ** : PropertyList() + activate PropertyList DATA_COLOUR + deactivate PropertyList + PropertyList --> Expenditure -- + Expenditure -> PropertyList : setValue() + activate PropertyList DATA_COLOUR + PropertyList --> Expenditure -- + Expenditure -> PropertyList : serialize() + activate PropertyList DATA_COLOUR + PropertyList --> Expenditure -- + + Expenditure --> SerializerFunctions -- +end +SerializerFunctions --> ExpenditureList -- +[<-- ExpenditureList -- + +@enduml diff --git a/docs/puml/StorageClassDiagram.puml b/docs/puml/StorageClassDiagram.puml new file mode 100644 index 0000000000..d60f723f20 --- /dev/null +++ b/docs/puml/StorageClassDiagram.puml @@ -0,0 +1,30 @@ +@startuml +!include Style.puml + +allowmixing +skinparam componentStyle rectangle +skinparam classAttributeIconSize 0 +hide circle + +skinparam class { +ArrowColor black +BorderColor black +} + +component MMM MMM_COLOUR + +component Storage { +class Storage +} + +class Storage STORAGE_COLOUR { +-storageFile: File + ++load(): User ++save(User: user): void +} + + +MMM -right-> "1" Storage : loads/saves > + +@enduml \ No newline at end of file diff --git a/docs/puml/Style.puml b/docs/puml/Style.puml new file mode 100644 index 0000000000..2001f8da62 --- /dev/null +++ b/docs/puml/Style.puml @@ -0,0 +1,14 @@ +!define LOGIC_COLOR #3333C4 +!define LOGIC_COLOR_T1 #7777DB +!define LOGIC_COLOR_T2 #5252CE +!define LOGIC_COLOR_T3 #1616B0 +!define LOGIC_COLOR_T4 #101086 + +!define MMM_COLOUR #dddddd +!define PARSER_COLOUR #00b0f0 +!define UI_COLOUR #92d050 +!define COMMANDS_COLOUR #ff5050 +!define STORAGE_COLOUR #ffc000 +!define HELPER_COLOUR #e6d6c2 +!define USERFINANCIAL_COLOUR #ca8dfd +!define DATA_COLOUR #aeed00 diff --git a/docs/puml/UiClassDiagram.puml b/docs/puml/UiClassDiagram.puml new file mode 100644 index 0000000000..e58dcd84bd --- /dev/null +++ b/docs/puml/UiClassDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include Style.puml + +allowmixing +skinparam componentStyle rectangle +skinparam classAttributeIconSize 0 +hide circle + +skinparam class { +ArrowColor black +BorderColor black +} + +component MMM MMM_COLOUR + +component Ui { +class Ui +class "{abstract}\nPrintStrings" +} + +class Ui UI_COLOUR { +{static} +PROMPT: String = ">" + ++printIntro(): void ++readInput(): String +} + +class "{abstract}\nPrintStrings" UI_COLOUR { +{static} +tips:String[] +} + +MMM --> "1" Ui +Ui -> "{abstract}\nPrintStrings": uses tips > + +@enduml diff --git a/docs/puml/UpdateCommandSequenceDiagram.puml b/docs/puml/UpdateCommandSequenceDiagram.puml new file mode 100644 index 0000000000..ba98068247 --- /dev/null +++ b/docs/puml/UpdateCommandSequenceDiagram.puml @@ -0,0 +1,51 @@ +@startuml +!include Style.puml + +hide footbox +skinparam sequenceMessageAlign center + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box UpdateCommand +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:UpdateCommand" as C COMMANDS_COLOUR +end box + +MMM -> C: executeCommand() +activate C COMMANDS_COLOUR + +alt hasExpensesFlag() +C -> C: updateExpenditure() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else hasCreditCardListFlag() +C -> C: updateCreditCard() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else hasIncomeListFlag() +C -> C: updateIncome() +activate C COMMANDS_COLOUR +C --> C +deactivate C + +else else +C --> MMM +note right +An exception containing warning messages is thrown +end note + +end +C --> MMM +deactivate C +deactivate MMM + +@enduml diff --git a/docs/puml/UpdateCreditCardSequenceDiagram.puml b/docs/puml/UpdateCreditCardSequenceDiagram.puml new file mode 100644 index 0000000000..47fdeec702 --- /dev/null +++ b/docs/puml/UpdateCreditCardSequenceDiagram.puml @@ -0,0 +1,53 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box updateCreditCard() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:UpdateCommand" as updateCommand COMMANDS_COLOUR +participant ":GeneralFunctions" as GeneralFunctions HELPER_COLOUR +participant ":CreditCard" as CreditCard USERFINANCIAL_COLOUR +participant ":CreditCardList" as CreditCardList DATA_COLOUR + +end box +hide footbox + +MMM -> updateCommand: executeCommand() +activate updateCommand COMMANDS_COLOUR + +updateCommand -> updateCommand: updateCreditCard() +activate updateCommand COMMANDS_COLOUR + +updateCommand -> GeneralFunctions: parseInputWithCommandFlag() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions + +create CreditCard +updateCommand -> CreditCard: CreditCard() +activate CreditCard USERFINANCIAL_COLOUR +CreditCard --> updateCommand +deactivate CreditCard + +updateCommand -> CreditCardList: set() +activate CreditCardList DATA_COLOUR +CreditCardList --> updateCommand +deactivate CreditCardList + +updateCommand --> updateCommand +deactivate updateCommand + +note left +Details of newly updated +expenditure is printed out +end note + +updateCommand --> MMM +deactivate updateCommand +@enduml diff --git a/docs/puml/UpdateExpenditureSequenceDiagram.puml b/docs/puml/UpdateExpenditureSequenceDiagram.puml new file mode 100644 index 0000000000..c4273f28a4 --- /dev/null +++ b/docs/puml/UpdateExpenditureSequenceDiagram.puml @@ -0,0 +1,83 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box updateExpenditure() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:UpdateCommand" as updateCommand COMMANDS_COLOUR +participant ":GeneralFunctions" as GeneralFunctions HELPER_COLOUR +participant ":AddCommandInputTest" as AddCommandInputTest HELPER_COLOUR +participant ":Expenditure" as Expenditure USERFINANCIAL_COLOUR +participant ":ExpenditureList" as ExpenditureList DATA_COLOUR + +end box +hide footbox + +MMM -> updateCommand: executeCommand() +activate updateCommand COMMANDS_COLOUR + +updateCommand -> updateCommand: updateExpenditure() +activate updateCommand COMMANDS_COLOUR + +updateCommand -> GeneralFunctions: parseInputWithCommandFlag() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions + + +updateCommand -> AddCommandInputTest: testUpdateExpenditureParameters() +activate AddCommandInputTest HELPER_COLOUR + + +AddCommandInputTest --> updateCommand +deactivate AddCommandInputTest +updateCommand --> updateCommand +deactivate updateCommand COMMANDS_COLOUR + +opt NEW_PAYMENT_METHOD == cash +updateCommand -> GeneralFunctions: capitalise(paymentMethod) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions +end + +updateCommand -> GeneralFunctions: capitalise(category) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions + +updateCommand -> GeneralFunctions: formatFloat(amount) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions + +updateCommand -> updateCommand: updatePaymentMethod() +activate updateCommand COMMANDS_COLOUR +updateCommand --> updateCommand +deactivate updateCommand + +create Expenditure +updateCommand -> Expenditure: Expenditure() +activate Expenditure USERFINANCIAL_COLOUR +Expenditure --> updateCommand +deactivate Expenditure + +updateCommand -> ExpenditureList: set() +activate ExpenditureList DATA_COLOUR +ExpenditureList --> updateCommand +deactivate ExpenditureList + +note left +Details of newly updated +expenditure is printed out +end note + +updateCommand --> MMM +deactivate updateCommand +@enduml diff --git a/docs/puml/UpdateIncomeSequenceDiagram.puml b/docs/puml/UpdateIncomeSequenceDiagram.puml new file mode 100644 index 0000000000..7df93b9ce3 --- /dev/null +++ b/docs/puml/UpdateIncomeSequenceDiagram.puml @@ -0,0 +1,66 @@ +@startuml +!include Style.puml + +skinparam sequence { +ArrowColor black +participantBorderColor black +LifelineBorderColor black +boxBorderColor black +} + +box updateIncome() +participant ":MMM" as MMM MMM_COLOUR +participant "CommandType:UpdateCommand" as updateCommand COMMANDS_COLOUR +participant ":GeneralFunctions" as GeneralFunctions HELPER_COLOUR +participant ":AddCommandInputTest" as AddCommandInputTest HELPER_COLOUR +participant ":Income" as Income USERFINANCIAL_COLOUR +participant ":IncomeList" as IncomeList DATA_COLOUR + +end box +hide footbox + +MMM -> updateCommand: executeCommand() +activate updateCommand COMMANDS_COLOUR + +updateCommand -> updateCommand: updateIncome() +activate updateCommand COMMANDS_COLOUR + +updateCommand -> GeneralFunctions: parseInputWithCommandFlag() +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions + + +updateCommand -> AddCommandInputTest: testUpdateIncomeParameters() +activate AddCommandInputTest HELPER_COLOUR + +AddCommandInputTest --> updateCommand +deactivate AddCommandInputTest + +updateCommand -> GeneralFunctions: capitalise(inputCategory) +activate GeneralFunctions HELPER_COLOUR +GeneralFunctions --> updateCommand +deactivate GeneralFunctions + +create Income +updateCommand -> Income: Income() +activate Income USERFINANCIAL_COLOUR +Income --> updateCommand +deactivate Income + +updateCommand -> IncomeList: set() +activate IncomeList DATA_COLOUR +IncomeList --> updateCommand +deactivate IncomeList + +updateCommand --> updateCommand +deactivate updateCommand + +note left +Details of newly updated +Income is printed out +end note + +updateCommand --> MMM +deactivate updateCommand +@enduml diff --git a/docs/team/danbaterisna.md b/docs/team/danbaterisna.md new file mode 100644 index 0000000000..afdae388e8 --- /dev/null +++ b/docs/team/danbaterisna.md @@ -0,0 +1,64 @@ +# Dan Baterisna - Project Portfolio Page + +## Overview +`MindMyMoney` (M3) is a desktop app for managing your personal finances, optimized for use via a Command Line +Interface (CLI). You can use it to track your expenditures across multiple payment methods, calculate monthly +expenditure, and set financial goals. + +
+ +## Contribution Summary + +### Code Contribution +Code Contribution can be viewed [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=danbaterisna&breakdown=true) + +Enhancements implemented: + +1. Implemented initial `UpdateCommand`. + - Accepted an index, new description and new amount for an expenditure, and updated +2. Added `PropertyList` + - List of key-value pairs that can be saved to and loaded from a string + - Forms underlying basis for de/serialization. +3. Implemented `Storage` component. + - Has `save` and `load` methods, which save a `User` to and load a `User` from a file, respectively. +4. Implemented `serialize`, `deserialize` and `deserializeFrom` methods for `UserFinancial` classes. + - `User`, `ExpenditureList`, `CreditCardList`, `IncomeList`, `Expenditure`, `CreditCard`, and `Income` + can now save themselves to and load themselves from either strings or `Scanner`s. +5. Implemented `SerializerFunctions`. + - Abstracts out common functionality needed by de/serializers, improving DRY and SLAP. + - Implements `addListToStringBuilder` and `convertInputToList` methods. +6. Implemented `ValidatorFunctions`. + - Abstracts out common functionality used to check that the content of the save file is valid. +7. Added JUnit tests. + - Added JUnit tests for `UpdateCommand`, `Storage`, `Expenditure`, and `PropertyList`. + - Updated `UpdateCommand` and `Expenditure` tests during transition to v2.0. + - Ensures that the code is bug free, and helps in regression testing future iterations. +8. Miscellaneous + - Wrote JavaDoc comments for classes and methods created. + - Fixed bugs raised during PE-D. + - Updated command formats shown by `help` command during transition to v2.1. + + +### UG Contribution +1. Added short explanation of save file format. + +### DG Contribution + +1. Added sequence diagrams for loading. + - Explains interaction between classes for loading, as well as hierarchical nature + of the loading design. +2. Added sequence diagrams for saving. + - Explains interaction between classes for saving, as well as hierarchical nature + of the saving design. + +### Team-Based Tasks Contribution + +1. Wrote manual testing instructions. + + +### Miscellaneous Contribution +PR reviews (with non-trivial comments): +[#41](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/41), [#69](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/69), +[#206](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/206) + +High number of bugs found (11) during PE-Dry testing for `AY2122S2-CS2113-T11-2` can be viewed [here](https://github.com/danbaterisna/ped/issues). \ No newline at end of file diff --git a/docs/team/glendonnotglen.md b/docs/team/glendonnotglen.md new file mode 100644 index 0000000000..fe5d938175 --- /dev/null +++ b/docs/team/glendonnotglen.md @@ -0,0 +1,63 @@ +# Ng Zhao Zhi Glendon - Project Portfolio Page + +## Overview +`MindMyMoney` (M3) is a desktop app for managing your personal finances, optimized for use via a Command Line +Interface (CLI). You can use it to track your expenditures across multiple payment methods, calculate monthly +expenditure, and set financial goals. + +## Contribution Summary + +### Code Contribution +Code Contribution can be viewed [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=glendonnotglen&breakdown=true). + +Enhancements implemented: +1. Added `Credit Card` class. + - Implemented methods for adding, listing, updating and deleting Credit Cards. + - Implemented relevant testing methods for input validations for adding and updating Credit Cards. +2. Added code structure for `ListCommand`. + - Added JUnit tests for `HelpCommand`, `AddCommand` and `DeleteCommand`. +3. Updated `HelpCommand` instructions for `help /e` and `help /cc`. + - Added JUnit tests for `HelpCommand`, `AddCommand` and `DeleteCommand`. +4. Updated `Parser.java` to detect varying sizes of flags. + - Adds versatility to our program's input. +5. Miscellaneous + - Added JUnit tests for `DeleteCommand`, `AddCommand`, `ListCommand` and `Parser`. + - Updated JUnit testing for multiple tests, including `CalculateInput`, `UpdateCommand`, and `HelpCommand` + - Wrote JavaDoc comments for a majority of methods. + - Fixed bugs raised during PE-Dry testing pertaining to `CreditCard` addition and improving invalid input handling. + +### UG Contribution +1. Wrote the `Credit Card` documentations for Users. + - Add, List, Update, and Delete commands. + - Updated Command Summary (Credit Card) table. + +2. Streamlined content page. + - Standardised content throughout the User Guide. + - Improved User Guide as per feedback from Professor. + - Proofread and reduced Grammatical errors. + +### DG Contribution +1. Wrote `Delete` section. + - Includes deleting `Expenditure`, `Credit Card`, and `Income`. + - Included Design considerations. + - Designed relevant command sequence diagrams to improve readability and understanding of Developer's Guide + +2. Wrote `Update` section. + - Includes update `Expenditure`, `Credit Card`, and `Income`. + - Included Design considerations. + - Designed relevant command sequence diagrams to improve readability and understanding of Developer's Guide + +3. Redo numerous sequence diagrams to standardise colours used for each Class and improved readability of diagrams + +### Team-Based tasks Contribution +1. Created and added members into `developers` team on GitHub. + +2. Proofread teammates' contributions to User Guide and Developer Guide. + + +### Miscellaneous Contribution +PR reviews (with non-trivial comments): +[#69](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/69), [#78](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/78), +[#162](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/162), [#201](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/201) + +Moderate number of bugs found (7) during PE-Dry testing for `AY2122S2-CS2113-T11-3` can be viewed [here](https://github.com/glendonnotglen/ped/issues). diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/khseah.md b/docs/team/khseah.md new file mode 100644 index 0000000000..0501a20333 --- /dev/null +++ b/docs/team/khseah.md @@ -0,0 +1,73 @@ +# Seah Kit Han - Project Portfolio Page + +## Overview +`MindMyMoney` (M3) is a desktop app for managing your personal finances, optimized for use via a Command Line +Interface (CLI). You can use it to track your expenditures across multiple payment methods, calculate monthly +expenditure, and set financial goals. + +## Contribution Summary + +### Code Contribution +Code Contribution can be viewed [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=khseah&breakdown=true). + +1. Set up the initial structure to build our code base upon. + - `MindMyMoney` class as the entry point, `Ui` class, `Parser` class and `Command` class. + - Allows the team to have a common foundation to build upon and sets the architecture structure. + +2. Added the `Ui` class. + - Implemented the `>` prompt to help the user differentiate between their input and the program's output. + - Sourced for 49 financial tips, of which 1 would be printed on startup, making it more interactive. + +3. Added Add, Delete, Update and List functionalities for `Income`. + - As a Personal Finance app, it is imperative that income can be tracked, so that users can monitor their income +inflow and outflow. + - Affected existing Add, Delete, Update and List commands as these commands were also used for `Expenditure` and +`CreditCard`. + +4. Added the `User` class. + - Abstracts out `ExpenditureList`, `CreditCardList` and `IncomeList` classes into 1 class, increasing OOP. + - Affected a majority of existing code as previous methods had the 3 lists as parameters. + +5. Miscellaneous. + - Added JUnit tests for `HelpCommand`, `AddCommand` and `DeleteCommand`. + - Fixed bugs raised during PE-Dry testing pertaining to `CreditCard` limit. + +### UG Contribution +1. Wrote the `Income` portion. + - Help, Add, List, Update and Delete. + - Command Summary (Income) table. + + +2. Streamlined content page. + - Shortened section headers and standardized action verbs (Display, Add, Modify etc). + +### DG Contribution +1. Wrote the `Architecture overview` portion. + - Added Fig 1 - Architecture Diagram and Fig 2 - Sequence Diagram. + +2. Wrote the `Ui` and `Parser Component overview` portion. + - Added Fig 3 - Ui Class Diagram and Fig 4 - Parser Class Diagram. + +3. Wrote the `Add Income implementation` portion. + - Added Fig 10 - Add Income Sequence Diagram. + +4. Wrote the `List Command implementation` portion. + - Added Fig 12 - List Command Sequence Diagram, Fig 13 - List Expenditure Sequence Diagram, Fig 14 - List Credit Card +Sequence Diagram and Fig 15 - List Income Sequence Diagram + +### Team-Based tasks Contribution +1. Set up Issue Tracker. + - Added `Issue type`, `Priority` and `Severity` labels. + - Created and assigned issues for `v1.0` based on user stories. + - Created and closed `v1.0` Milestone. + +2. Released `v1.0` and `v2.0` JAR files. + +### Miscellaneous Contribution +PR reviews (with non-trivial comments): +[#39](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/39), [#46](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/46), +[#72](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/72), [#86](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/86), +[#87](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/87), [#143](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/143). + +[DG review](https://github.com/nus-cs2113-AY2122S2/tp/pull/6/files/4125efa69fbb7ffda1b2ade950ec48b6e80f5baf) for `Simplst`, +[Bugs found](https://github.com/khseah/ped/issues) during PE-Dry testing for `Spendvelope`. diff --git a/docs/team/limjierui.md b/docs/team/limjierui.md new file mode 100644 index 0000000000..5ea457d86b --- /dev/null +++ b/docs/team/limjierui.md @@ -0,0 +1,70 @@ +# Lim Jie Rui - Project Portfolio Page + +## Overview +`MindMyMoney` (M3) is a desktop app for managing your personal finances, optimized for use via a Command Line +Interface (CLI). You can use it to track your expenditures across multiple payment methods, calculate monthly +expenditure, and set financial goals. + +## Contribution Summary + +### Code Contribution +Code Contribution can be viewed [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=limjierui&breakdown=true) + +Enhancements implemented: + +1. Refactor initial code to add more abstractions and OOP. + - Added `expenditureList` class to tackle the global list issue during initial testing. + - Refactored all command classes to have more OOP and abstractions. + +2. Added `bye` and `delete` command. + - Added `bye` class that allows users to exit when they want to end the program. + - Set up the `delete` class to delete any items in the expenditure list with an index. + +3. Added `MindMyMoneyException` class. + - Set up `MindMyMoneyException` class to allow exceptions to be thrown as MindMyMoneyException. + +4. Enhanced `CalculateInputCommand` command. + - Added a breakdown of expenses by categories. + - Enhanced visual appeal by displaying details in a bar chart format. + - Enhanced command to allow users to search by date, month or year. + +5. Enhanced `addCommand`,`listCommand` and `updateCommand` commands. + - Added enhancements to check if the date input is in the correct format and before current date of user. + - Added enhancements to check if the date input is a valid date in a leap year or non leap year. + - Enhanced `listCommand` to list expenditures by date specified (date, month or year). + - Added checks for `updateCommand` to prevent user from updating an expenditure if all field details are the same. + +6. Added and updated JUnit tests. + - Tests for `deleteCommand`, `calculateInputCommand`, `listCommand`, `helpCommand`, `updateCommand`, `addCommand` + +7. Miscellaneous + - Fixed formatting issues and wrong error message outputs in JUnit tests. + - Standardised and coded clearer output messages across different commands. + - Wrote JavaDoc comments for a majority of methods. + - Fixed bugs raised during PE-D. + +### UG Contribution +1. Added initial example pictures into the UG for different commands. +2. Standardised command input parameters. +3. Edited examples across commands to have a flow that had a user story to it. +4. Checked and edited wrong example outputs. + +### DG Contribution +1. Wrote and explained the `Command` component. + - Added command component class diagram and how it works. +2. Wrote and explained the `Storage` component. + - Added storage component class diagram and how it works. +3. Wrote and explained the `CalculateInputCommand` component. + - Added CalculateInputCommand sequence diagram and how it works. + +### Team-Based Tasks Contribution +1. Issued v2.1 issues to members and assigned v2.1 milestones to issues after PE-D. +2. Refactored code and added abstractions to allow members to add JUnit tests and functional code more easily in future implementations. +3. Standardised sequence and class diagrams to have the same format. + +### Miscellaneous Contribution +PR reviews (with non-trivial comments): +[#34](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/34), [#69](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/69), +[#77](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/77), [#90](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/90), +[#213](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/213)
+High number of [bugs found](https://github.com/limjierui/ped/issues) (16) during PE-Dry testing for `AY2122S2-CS2113-F10-1`. \ No newline at end of file diff --git a/docs/team/seanhowb.md b/docs/team/seanhowb.md new file mode 100644 index 0000000000..c61369fc70 --- /dev/null +++ b/docs/team/seanhowb.md @@ -0,0 +1,100 @@ +# Ho Wen Bin, Sean - Project Portfolio Page + +## Overview + +`MindMyMoney` (M3) is a desktop app for managing your personal finances, optimized for use via a Command Line +Interface (CLI). You can use it to track your expenditures across multiple payment methods, calculate monthly +expenditure, and set financial goals. +
+ +## Contribution Summary + +### Code Contribution + +Code Contribution can be +viewed [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=SeanHoWB&breakdown=true). + +Enhancements implemented: + +1. Set up the initial structure to build our code base upon. + - `AddCommand` class to allow users to add commands into `MindMyMoney`. + - `CalculationInputCommand` class to allow users to calculate their expenses. + - `userfinancial` package containing `Expenditure` and `CreditCard` classes. + - `helper` package containing helper functions used throughout the codebase, improving SLAP by extracting out code + from classes. + - Allows the team to use functions and classes essential to `MindMyMoney`. + +2. Added the `AddCommand` class. + - Allows users to track and add their expenditure in `MindMyMoney`, and see a summary of all the inputs they have + added. + - Added its relevant `AddCommandInputTests` that serves as input validation and checks the input of each field in + the add command for its validity. + - This command was challenging as it has many fields that needed to be validated and formatted. It also interacts + with other commands in the codebase which made it more complicated. + +3. Set up the `CalculationInputCommand` class. + - Allows users to calculate the total amount of expenditures, according to date. + - Added `Calculations` class to perform the calculations needed by `CalculationInputCommand`. + +4. Added`Expenditure` class. + - As a key functionality of `MindMyMoney`, users can now track their expenditures and get their total expenditure. + +5. Added `CreditCard` class. + - Being part of the value proposition of `MindMyMoney`, it allows users to track their multiple forms of payment. + +6. Added `GeneralFunction` class + - Allows the team to call frequently used functions in `MindMyMoney` from just one class, increases SLAP. + - Implemented `findItemsInList` function that aided the search for expenditure by date and by category in + `calculateExpenditurePerMonth`. + - Implemented `parseInputWithCommandFlag` to parse user input containing flags. This was used throughout the code + across many commands. + - Implemented `capitalise`, `formatFloat` functions, to format user input. + +7. Added JUnit tests + - Added JUnit tests to `AddCommand`, `ListCommand`, `UpdateCommand`, `CalculateInputCommand`, + `GeneralFunctions`, `HelpCommand`, `DeleteCommand`, `Parser`, `ByeCommand`, `MindMyMoney`, `Ui`. + - Increased the test coverage for these classes to 88% classes and 87% lines covered. + - Implemented functions to allow capturing of terminal output, used throughout other JUnit tests. + +8. Miscellaneous + - Fixed bugs raised during PE-Dry testing pertaining to inconsistent UG images. + - `ExpenditureCategoryType` enum for containing the types of category of expenditure. + - `ExpenditureFields` enum for containing the fields in an add expenditure command. + - `Indexes` class containing constants referencing indexes used in arrays. + +### UG Contribution + +1. Wrote the `Add an expenditure` portion. + - Explained the flags of expenditure and how to use it. + +2. Wrote the Introduction and reformatted the guide. + - Wrote the introduction, using the user guide and quick start. + - Changed the way expected output is displayed, from images to text form for better formatting. + - Formatted the headers and the `Format` and `Expected Outcome` field. + +### DG Contribution + +1. Contributed to the `Add Command` portion. + - Added `Add Expenditure` portion. + - Added the initial Fig 7 - AddCommand Sequence Diagram and Fig 8 - Add Expenditure Command Sequence Diagram. + +2. Wrote the `Introduction` and `Appendix Requirements`. + - Added Product Scope, Non-Functional Requirements, Glossary. + - Added the purpose, acknowledgements and using the developer guide. + +### Team-Based tasks Contribution + +1. Issued issues and milestones. + - Created and assigned issues for `v2.0` based on user stories. + - Created and closed `v2.0` Milestone. + - Opened `v2.1` Milestone. + +### Miscellaneous Contribution + +PR reviews (with non-trivial comments): +[#38](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/38), [#78](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/78) +, +[#51](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/51), [#86](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/86) +, +[#74](https://github.com/AY2122S2-CS2113T-T10-4/tp/pull/74), + 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/seedu/mindmymoney/MindMyMoney.java b/src/main/java/seedu/mindmymoney/MindMyMoney.java new file mode 100644 index 0000000000..39603056bc --- /dev/null +++ b/src/main/java/seedu/mindmymoney/MindMyMoney.java @@ -0,0 +1,66 @@ +package seedu.mindmymoney; + +import seedu.mindmymoney.command.Command; +import seedu.mindmymoney.userfinancial.User; + +import java.io.File; + +/** + * Represents the entry point of the MindMyMoney program. Initializes the program and starts interaction with the + * user. + */ +public class MindMyMoney { + private final Ui ui; + private User user; + private final Storage storage; + private static final String STORAGE_FILENAME = "data.txt"; + + public MindMyMoney() { + Storage savedStorage; + ui = new Ui(); + user = new User(); + try { + savedStorage = new Storage(new File(STORAGE_FILENAME)); + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + savedStorage = null; + } + storage = savedStorage; + } + + public void run() { + ui.printIntro(); + + if (storage != null) { + try { + user = storage.load(); + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + System.out.println(System.lineSeparator()); + } + } + + boolean isExit = false; + while (!isExit) { + try { + String input = ui.readInput(); + Command commandType = Parser.parseCommand(input, user); + commandType.executeCommand(); + + isExit = commandType.isExit(); + + if (storage != null) { + storage.save(user); + } + + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + System.out.print(System.lineSeparator()); + } + } + } + + public static void main(String[] args) { + new MindMyMoney().run(); + } +} diff --git a/src/main/java/seedu/mindmymoney/MindMyMoneyException.java b/src/main/java/seedu/mindmymoney/MindMyMoneyException.java new file mode 100644 index 0000000000..31c5682449 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/MindMyMoneyException.java @@ -0,0 +1,10 @@ +package seedu.mindmymoney; + +/** + * Represents exceptions that are exclusive to MindMyMoney program. + */ +public class MindMyMoneyException extends Exception { + public MindMyMoneyException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/seedu/mindmymoney/Parser.java b/src/main/java/seedu/mindmymoney/Parser.java new file mode 100644 index 0000000000..125fbb5c9c --- /dev/null +++ b/src/main/java/seedu/mindmymoney/Parser.java @@ -0,0 +1,67 @@ +package seedu.mindmymoney; + +import seedu.mindmymoney.command.AddCommand; +import seedu.mindmymoney.command.CalculateInputCommand; +import seedu.mindmymoney.command.Command; +import seedu.mindmymoney.command.HelpCommand; +import seedu.mindmymoney.command.ByeCommand; +import seedu.mindmymoney.command.UpdateCommand; +import seedu.mindmymoney.command.DeleteCommand; +import seedu.mindmymoney.command.ListCommand; +import seedu.mindmymoney.helper.GeneralFunctions; +import seedu.mindmymoney.userfinancial.User; + +import static seedu.mindmymoney.constants.Flags.EMPTY_PARAMETER; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENSES; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; + +/** + * Represents the input parser and deals with making sense of user commands. + */ +public class Parser { + + /** + * Returns a Command object with respect to their input. The command object can then be executed to perform + * the said command. + * + * @param input The command to be parsed. + * @param user The user object, which contains income, expenditure and credit card list. + * @return Command object with respect to user's input. + */ + public static Command parseCommand(String input, User user) { + try { + String[] parsedInput = GeneralFunctions.parseInput(input); + assert parsedInput[INDEX_OF_FIRST_ITEM] != null : "First element in parsedInput is null"; + + switch (parsedInput[INDEX_OF_FIRST_ITEM].toLowerCase()) { + case "help": + if (hasAdditionalParameters(parsedInput)) { + return new HelpCommand(true, parsedInput[INDEX_OF_SECOND_ITEM]); + } + return new HelpCommand(true, EMPTY_PARAMETER); + case "bye": + return new ByeCommand(); + case "add": + return new AddCommand(parsedInput[INDEX_OF_SECOND_ITEM], user); + case "update": + return new UpdateCommand(parsedInput[INDEX_OF_SECOND_ITEM], user); + case "list": + return new ListCommand(parsedInput[INDEX_OF_SECOND_ITEM], user); + case "delete": + return new DeleteCommand(input, user); + case "calculate": + return new CalculateInputCommand(parsedInput[INDEX_OF_SECOND_ITEM], user); + default: + return new HelpCommand(false, FLAG_OF_EXPENSES); + } + } catch (ArrayIndexOutOfBoundsException e) { + System.out.print(""); + } + return new HelpCommand(false, FLAG_OF_EXPENSES); + } + + private static boolean hasAdditionalParameters(String[] input) { + return input.length > 1; + } +} diff --git a/src/main/java/seedu/mindmymoney/Storage.java b/src/main/java/seedu/mindmymoney/Storage.java new file mode 100644 index 0000000000..cff4ed9a23 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/Storage.java @@ -0,0 +1,69 @@ +package seedu.mindmymoney; + +import seedu.mindmymoney.userfinancial.User; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +/** + * Class for handling loading and saving of expenditure lists. + */ +public class Storage { + private File storageFile; + + public Storage(File storageFile) throws MindMyMoneyException { + this.storageFile = storageFile; + if (!storageFile.exists()) { + try { + storageFile.createNewFile(); + } catch (IOException e) { + throw new MindMyMoneyException("WARNING: Failed to create save file. " + + "You may still use MindMyMoney; however, your data may not be saved."); + } + } + } + + /** + * Loads information from the save file. If the file does not exist, or if there is + * an error when reading the file, return an empty list, and print a warning message. + * @return The saved list. + * @throws MindMyMoneyException if an error occurs while reading the file, or if the file has an invalid format. + */ + public User load() throws MindMyMoneyException { + try { + Scanner scanner = new Scanner(storageFile); + User savedUser = User.deserializeFrom(scanner); + scanner.close(); + return savedUser; + } catch (FileNotFoundException e) { + throw new MindMyMoneyException("WARNING: Save file not found. MindMyMoney cannot read your saved data."); + } catch (MindMyMoneyException e) { + throw new MindMyMoneyException("WARNING: Error when reading save data: " + e.getMessage() + "\n" + + "MindMyMoney will create a new save file, possibly overwriting the existing file.\n" + + "If you have important data stored there, make a copy of the current save file."); + } + } + + /** + * Saves the information associated with the given User. If the file does not exist, or if there is an error + * when saving to the file, print a warning message. + * @param user User whose lists need to be saved. + * @throws MindMyMoneyException if an error occurs while saving. + */ + public void save(User user) throws MindMyMoneyException { + try { + BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(storageFile)); + bufferedWriter.write(user.serialize()); + bufferedWriter.flush(); + bufferedWriter.close(); + } catch (FileNotFoundException e) { + throw new MindMyMoneyException("WARNING: Save file not found. MindMyMoney cannot save your data."); + } catch (IOException e) { + throw new MindMyMoneyException("WARNING: Error when saving expenditure list: " + e.getMessage() + "\n"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/Ui.java b/src/main/java/seedu/mindmymoney/Ui.java new file mode 100644 index 0000000000..2e5a6104ad --- /dev/null +++ b/src/main/java/seedu/mindmymoney/Ui.java @@ -0,0 +1,51 @@ +package seedu.mindmymoney; + +import java.util.Scanner; +import java.util.Random; + +import static seedu.mindmymoney.constants.PrintStrings.tips; + +/** + * Deals with interactions with the user. + */ +public class Ui { + public static final String PROMPT = "> "; + + private final Scanner in; + private final Random rand; + + public Ui() { + this.in = new Scanner(System.in); + this.rand = new Random(); + } + + /** + * Prints logo and welcome message at startup. + */ + public void printIntro() { + String logo = " __ __ _ _ __ __ __ __\n" + + "| \\/ (_)_ _ __| | \\/ |_ _| \\/ |___ _ _ ___ _ _\n" + + "| |\\/| | | ' \\/ _` | |\\/| | || | |\\/| / _ \\ ' \\/ -_) || |\n" + + "|_| |_|_|_||_\\__,_|_| |_|\\_, |_| |_\\___/_||_\\___|\\_, |\n" + + " |__/ |__/"; + + System.out.println(System.lineSeparator()); + System.out.println(logo); + int randomIndex = rand.nextInt(48); + assert randomIndex >= 0 && randomIndex <= 48 : "randomIndex is not within the bounds"; + System.out.println("<< " + tips[randomIndex] + " >>" + System.lineSeparator()); + System.out.println("Welcome to MindMyMoney"); + System.out.println("What can I do for you?" + System.lineSeparator()); + } + + /** + * Prints the prompt and reads user's input. + * + * @return User's input. + */ + public String readInput() { + System.out.print(PROMPT); + String input = in.nextLine(); + return input; + } +} diff --git a/src/main/java/seedu/mindmymoney/command/AddCommand.java b/src/main/java/seedu/mindmymoney/command/AddCommand.java new file mode 100644 index 0000000000..b3e3a21f44 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/AddCommand.java @@ -0,0 +1,209 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Income; +import seedu.mindmymoney.userfinancial.User; + +import static seedu.mindmymoney.constants.Flags.FLAG_END_VALUE; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_AMOUNT; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CARD_LIMIT; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CARD_NAME; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CASHBACK; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CATEGORY; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CREDIT_CARD; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_DESCRIPTION; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENSES; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_INCOME; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_PAYMENT_METHOD; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_TIME; +import static seedu.mindmymoney.helper.AddCommandInputTests.testExpenditureParameters; +import static seedu.mindmymoney.helper.AddCommandInputTests.testIncomeParameters; +import static seedu.mindmymoney.helper.AddCommandInputTests.testCreditCardParameters; +import static seedu.mindmymoney.helper.GeneralFunctions.capitalise; +import static seedu.mindmymoney.helper.GeneralFunctions.parseInputWithCommandFlag; +import static seedu.mindmymoney.helper.GeneralFunctions.formatFloat; + +/** + * Represents the Add command. + */ +public class AddCommand extends Command { + private String addInput; + public ExpenditureList expenditureList; + public CreditCardList creditCardList; + public IncomeList incomeList; + + public AddCommand(String addInput, User user) { + this.addInput = addInput; + this.expenditureList = user.getExpenditureListArray(); + this.creditCardList = user.getCreditCardListArray(); + this.incomeList = user.getIncomeListArray(); + } + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return false; + } + + /** + * Indicates whether the add command is to add an expenditure by checking for the expenditure flag. + * + * @return true if the /e flag is present, false otherwise. + */ + private boolean hasExpenditureFlag() { + return addInput.startsWith(FLAG_OF_EXPENSES); + } + + /** + * Indicates whether the add command is to add a credit card by looking for the credit card flag. + * + * @return true if the credit card flag is present, false otherwise. + */ + private boolean hasCreditCardFlag() { + return addInput.startsWith(FLAG_OF_CREDIT_CARD); + } + + /** + * Updates the total expenditure field in the credit card specified in the expenditure item and returns + * the balance left. + * + * @param cardName Name of credit card to be updated. + * @param amount amount of new expenditure. + * @return The credit card balance left. + * @throws MindMyMoneyException when the card is not found in user's credit card list. + */ + private float updateCreditCardTotalExpenditure(String cardName, float amount) throws MindMyMoneyException { + CreditCard creditCard = creditCardList.get(cardName); + if (creditCard == null) { + throw new MindMyMoneyException("Invalid Card Name!"); + } + creditCard.addExpenditure(amount); + return creditCard.getBalanceLeft(); + } + + /** + * Indicates whether the add command is to add an income by looking for the /i flag. + * + * @return true if the /i flag is present, false otherwise. + */ + private boolean hasIncomeFlag() { + return addInput.startsWith(FLAG_OF_INCOME); + } + + /** + * Inserts an Expenditure object into user's list of expenditure(s). + * + * @throws MindMyMoneyException when inputs are invalid or flags are missing. + */ + public void addExpenditure() throws MindMyMoneyException { + String paymentMethod = parseInputWithCommandFlag(addInput, FLAG_OF_PAYMENT_METHOD, FLAG_OF_CATEGORY).trim(); + String inputCategory = parseInputWithCommandFlag(addInput, FLAG_OF_CATEGORY, FLAG_OF_DESCRIPTION).trim(); + String description = parseInputWithCommandFlag(addInput, FLAG_OF_DESCRIPTION, FLAG_OF_AMOUNT).trim(); + String amountAsString = parseInputWithCommandFlag(addInput, FLAG_OF_AMOUNT, FLAG_OF_TIME).trim(); + String inputTime = parseInputWithCommandFlag(addInput, FLAG_OF_TIME, FLAG_END_VALUE).trim(); + testExpenditureParameters(paymentMethod, inputCategory, description, amountAsString, inputTime, creditCardList); + + if (capitalise(paymentMethod).equals("Cash")) { + paymentMethod = capitalise(paymentMethod); + } + String category = capitalise(inputCategory); + float amountAsFloat = Float.parseFloat(amountAsString); + float amountFloat = formatFloat(amountAsFloat); + expenditureList.add(new Expenditure(paymentMethod, category, description, amountFloat, inputTime)); + + System.out.println("Successfully added: \n\n" + + "Description: " + description + "\n" + + "Amount: $" + String.format("%.2f", amountFloat) + "\n" + + "Category: " + category + "\n" + + "Payment method: " + paymentMethod + "\n" + + "Date: " + inputTime + "\n\n" + + "into the account"); + + if (!paymentMethod.equals("Cash")) { + float balanceLeft = updateCreditCardTotalExpenditure(paymentMethod, amountFloat); + System.out.printf(paymentMethod + " has a balance of $%.2f left%n", balanceLeft); + } + System.out.print(System.lineSeparator()); + } + + /** + * Inserts a CreditCard object into user's list of credit card(s). + * + * @throws MindMyMoneyException Exception thrown when input is invalid + */ + public void addCreditCard() throws MindMyMoneyException { + final String cardName = parseInputWithCommandFlag(addInput, FLAG_OF_CARD_NAME, + FLAG_OF_CASHBACK).trim(); + final String cashBack = parseInputWithCommandFlag(addInput, FLAG_OF_CASHBACK, + FLAG_OF_CARD_LIMIT).trim(); + final String cardLimit = parseInputWithCommandFlag(addInput, FLAG_OF_CARD_LIMIT, + FLAG_END_VALUE).trim(); + testCreditCardParameters(cardName, cashBack, cardLimit, creditCardList); + Float cashBackAsFloat = formatFloat(Float.parseFloat(cashBack)); + Float cardLimitAsFloat = formatFloat(Float.parseFloat(cardLimit)); + creditCardList.add(new CreditCard(cardName, cashBackAsFloat, cardLimitAsFloat)); + + System.out.println("Successfully added: \n\n" + + "Credit card: " + cardName + "\n" + + "Cash back: " + String.format("%.2f", cashBackAsFloat) + "%\n" + + "Card limit: $" + String.format("%.2f", cardLimitAsFloat) + "\n\n" + + "into the account"); + System.out.print(System.lineSeparator()); + } + + /** + * Inserts an Income object into user's list of income(s). + * + * @throws MindMyMoneyException when the input amount is not a number. + */ + public void addIncome() throws MindMyMoneyException { + String amountAsString = parseInputWithCommandFlag(addInput, FLAG_OF_AMOUNT, FLAG_OF_CATEGORY).trim(); + + try { + int amountAsInt = Integer.parseInt(amountAsString); + String inputCategory = parseInputWithCommandFlag(addInput, FLAG_OF_CATEGORY, FLAG_END_VALUE).trim(); + testIncomeParameters(amountAsInt, inputCategory); + String category = capitalise(inputCategory); + + incomeList.add(new Income(amountAsInt, category)); + + System.out.print("Successfully added: \n\n" + + "Amount: $" + amountAsInt + "\n" + + "Category: " + category + "\n\n" + + "into the account"); + System.out.println(System.lineSeparator()); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Income must be a whole number or your income is too high!"); + } + } + + /** + * Inserts either an Expenditure, CreditCard or Income object into the user's list based on the input. + * + * @throws MindMyMoneyException when an invalid command is received, along with its corresponding error message. + */ + @Override + public void executeCommand() throws MindMyMoneyException { + if (hasExpenditureFlag()) { + addExpenditure(); + } else if (hasCreditCardFlag()) { + addCreditCard(); + } else if (hasIncomeFlag()) { + addIncome(); + } else { + throw new MindMyMoneyException("You are missing a flag in your command\n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/command/ByeCommand.java b/src/main/java/seedu/mindmymoney/command/ByeCommand.java new file mode 100644 index 0000000000..6c029b7aca --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/ByeCommand.java @@ -0,0 +1,26 @@ +package seedu.mindmymoney.command; + +/** + * Represents the Bye command. + */ +public class ByeCommand extends Command { + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return true; + } + + /** + * Prints the bye message. + */ + @Override + public void executeCommand() { + System.out.print("Bye, hope to see you again!" + + System.lineSeparator()); + } +} diff --git a/src/main/java/seedu/mindmymoney/command/CalculateInputCommand.java b/src/main/java/seedu/mindmymoney/command/CalculateInputCommand.java new file mode 100644 index 0000000000..a0c3af525b --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/CalculateInputCommand.java @@ -0,0 +1,57 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.helper.GeneralFunctions; +import seedu.mindmymoney.userfinancial.User; + +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENDITURE_PER_MONTH; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; +import static seedu.mindmymoney.helper.Calculations.calculateExpenditure; + +/** + * Represents the Calculate command. + */ +public class CalculateInputCommand extends Command { + private String calculateInput; + public ExpenditureList expenditureList; + + public CalculateInputCommand(String calculateInput, User user) { + this.calculateInput = calculateInput; + this.expenditureList = user.getExpenditureListArray(); + } + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return false; + } + + /** + * Parses the input for calculate command. + * + * @throws MindMyMoneyException when inputs are invalid or flags are missing. + */ + @Override + public void executeCommand() throws MindMyMoneyException { + try { + String[] parsedCalculateInput = GeneralFunctions.parseInput(calculateInput); + assert parsedCalculateInput[INDEX_OF_FIRST_ITEM] != null + : "First element in parsedCalculateInput is null"; + switch (parsedCalculateInput[INDEX_OF_FIRST_ITEM].toLowerCase()) { + case FLAG_OF_EXPENDITURE_PER_MONTH: + calculateExpenditure(parsedCalculateInput[INDEX_OF_SECOND_ITEM], expenditureList); + break; + default: + throw new MindMyMoneyException("Remember to use a proper flag!"); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new MindMyMoneyException("Missing input after command!"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/command/Command.java b/src/main/java/seedu/mindmymoney/command/Command.java new file mode 100644 index 0000000000..c2eb9166f9 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/Command.java @@ -0,0 +1,22 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; + +/** + * Represents an executable command. This class cannot be instantiated and serves as a Parent class for specific + * Command classes to inherit from. + */ +public abstract class Command { + + /** + * Executes the given command. This method is to be implemented by child classes. + */ + public abstract void executeCommand() throws MindMyMoneyException; + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + public abstract boolean isExit(); +} diff --git a/src/main/java/seedu/mindmymoney/command/DeleteCommand.java b/src/main/java/seedu/mindmymoney/command/DeleteCommand.java new file mode 100644 index 0000000000..73d58bcc55 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/DeleteCommand.java @@ -0,0 +1,218 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; + +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.User; + +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CREDIT_CARD; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENSES; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_INCOME; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_THIRD_ITEM; +import static seedu.mindmymoney.constants.Indexes.LIST_INDEX_CORRECTION; +import static seedu.mindmymoney.constants.Indexes.MINIMUM_CREDIT_CARD_COMMAND_LENGTH; +import static seedu.mindmymoney.constants.Indexes.MINIMUM_EXPENDITURE_COMMAND_LENGTH; +import static seedu.mindmymoney.constants.Indexes.MINIMUM_INCOME_COMMAND_LENGTH; + +/** + * Represents the Delete command. + */ +public class DeleteCommand extends Command { + private String input; + public ExpenditureList expenditureList; + public CreditCardList creditCardList; + public IncomeList incomeList; + + public DeleteCommand(String input, User user) { + this.input = input; + this.expenditureList = user.getExpenditureListArray(); + this.creditCardList = user.getCreditCardListArray(); + this.incomeList = user.getIncomeListArray(); + } + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return false; + } + + /** + * Indicates whether the delete command is to delete a credit card by looking for the /e flag. + * + * @return true if the /cc flag is present, false otherwise. + */ + private boolean hasExpenditureFlag() { + return input.contains(FLAG_OF_EXPENSES); + } + + /** + * Indicates whether the delete command is to delete a credit card by looking for the /cc flag. + * + * @return true if the /cc flag is present, false otherwise. + */ + private boolean hasCreditCardFlag() { + return input.contains(FLAG_OF_CREDIT_CARD); + } + + /** + * Indicates whether the delete command is to delete an income by looking for the /i flag. + * + * @return true if the /i flag is present, false otherwise. + */ + private boolean hasIncomeFlag() { + return input.contains(FLAG_OF_INCOME); + } + + /** + * Updates the total expenditure field in the credit card specified in the expenditure item. + * + * @param cardName Name of credit card to be updated. + * @param amount amount of new expenditure. + */ + private void updateCreditCardTotalExpenditure(String cardName, float amount) { + CreditCard creditCard = creditCardList.get(cardName); + if (creditCard != null) { + creditCard.deductExpenditure(amount); + } + } + + /** + * Removes an expenditure from user's list of expenditure(s). + * + * @throws MindMyMoneyException when expenditure list is empty or an invalid command is received. + */ + public void deleteExpenditure() throws MindMyMoneyException { + try { + if (expenditureList.isEmpty()) { + throw new MindMyMoneyException(System.lineSeparator() + + "Please add something to your expenditure list first:)" + + System.lineSeparator()); + } + + String[] splitMessage = input.split(" "); + if (splitMessage.length != MINIMUM_EXPENDITURE_COMMAND_LENGTH) { + throw new MindMyMoneyException(System.lineSeparator() + "Please check your input parameters\n" + + "Enter 'delete /e [INDEX]' to remove an expenditure from your list.\n"); + } + + String getNumber = splitMessage[INDEX_OF_THIRD_ITEM]; + int positionToDelete = Integer.parseInt(getNumber) + LIST_INDEX_CORRECTION; + Expenditure expenditure = expenditureList.get(positionToDelete); + + String paymentMethod = expenditure.getPaymentMethod(); + if (!paymentMethod.equals("Cash")) { + updateCreditCardTotalExpenditure(paymentMethod, expenditure.getAmount()); + } + + System.out.println("I have removed " + + expenditure.getDescription() + + " of $" + String.format("%.2f",expenditure.getAmount()) + + " from the account" + System.lineSeparator()); + expenditureList.delete(positionToDelete); + assert positionToDelete >= 0 : "Index should always be >= 0"; + + } catch (NumberFormatException e) { + throw new MindMyMoneyException("INDEX must be a number"); + } catch (IndexOutOfBoundsException e) { + throw new MindMyMoneyException("Please input a valid index"); + } + } + + /** + * Removes a credit card from user's list of credit card(s). + * + * @throws MindMyMoneyException when credit card list is empty or an invalid command is received. + */ + public void deleteCreditCard() throws MindMyMoneyException { + try { + if (creditCardList.isEmpty()) { + throw new MindMyMoneyException(System.lineSeparator() + + "Please add something to your credit card list first:)" + + System.lineSeparator()); + } + + String[] splitMessage = input.split(" "); + if (splitMessage.length != MINIMUM_CREDIT_CARD_COMMAND_LENGTH) { + throw new MindMyMoneyException(System.lineSeparator() + "Please input a number\n" + + "For eg. 'delete /cc 2' to remove the second credit card on your list.\n"); + } + + String getNumber = splitMessage[INDEX_OF_THIRD_ITEM]; + int positionToDelete = Integer.parseInt(getNumber) + LIST_INDEX_CORRECTION; + + System.out.println("I have removed " + + creditCardList.get(positionToDelete).getNameOfCard() + + " from your list of credit card(s)." + System.lineSeparator()); + creditCardList.delete(positionToDelete); + assert positionToDelete >= 0 : "Index should always be >= 0"; + + } catch (NumberFormatException e) { + throw new MindMyMoneyException("INDEX must be a number"); + } catch (IndexOutOfBoundsException e) { + throw new MindMyMoneyException("Please input a valid index"); + } + } + + /** + * Removes an income from user's list of income(s). + * + * @throws MindMyMoneyException when income list is empty or an invalid command is received. + */ + public void deleteIncome() throws MindMyMoneyException { + try { + if (incomeList.isEmpty()) { + throw new MindMyMoneyException(System.lineSeparator() + + "Please add something to your income list first:)" + + System.lineSeparator()); + } + + String[] splitMessage = input.split(" "); + if (splitMessage.length != MINIMUM_INCOME_COMMAND_LENGTH) { + throw new MindMyMoneyException(System.lineSeparator() + "Please input a number\n" + + "For eg. 'delete /i 2' to remove the second income on your list.\n"); + } + + String getNumber = splitMessage[INDEX_OF_THIRD_ITEM]; + int positionToDelete = Integer.parseInt(getNumber) + LIST_INDEX_CORRECTION; + + System.out.println("I have removed " + + incomeList.get(positionToDelete).getCategory() + + " from your list of income(s)." + System.lineSeparator()); + incomeList.delete(positionToDelete); + + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Input a number!"); + } catch (NullPointerException | IndexOutOfBoundsException e) { + throw new MindMyMoneyException("Input a valid number!"); + } + } + + /** + * Removes an expenditure, credit card or income from the user's list based on the input. + * + * @throws MindMyMoneyException when an invalid command is received, along with its corresponding error message. + */ + @Override + public void executeCommand() throws MindMyMoneyException { + if (hasExpenditureFlag()) { + deleteExpenditure(); + } else if (hasCreditCardFlag()) { + deleteCreditCard(); + } else if (hasIncomeFlag()) { + deleteIncome(); + } else { + throw new MindMyMoneyException("You are missing a flag in your command\n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/command/HelpCommand.java b/src/main/java/seedu/mindmymoney/command/HelpCommand.java new file mode 100644 index 0000000000..9cc32e38ec --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/HelpCommand.java @@ -0,0 +1,145 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; + +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CREDIT_CARD; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENSES; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_INCOME; +import static seedu.mindmymoney.constants.Flags.EMPTY_PARAMETER; + +/** + * Represents the Help command. This class also serves as a dummy class to return when an invalid command is + * received. + */ +public class HelpCommand extends Command { + protected boolean isFromUser; + public String helpInput; + + public HelpCommand(boolean isFromUser, String helpInput) { + this.isFromUser = isFromUser; + this.helpInput = helpInput; + } + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return false; + } + + /** + * Indicates whether the help command is for expenses by looking for the /e flag. + * + * @return true if the /e flag is present, false otherwise. + */ + private boolean hasExpensesFlag() { + return helpInput.equals(FLAG_OF_EXPENSES); + } + + /** + * Indicates whether the help command is for credit card by looking for the /cc flag. + * + * @return true if the /cc flag is present, false otherwise. + */ + private boolean hasCreditCardListFlag() { + return FLAG_OF_CREDIT_CARD.equals(helpInput); + } + + /** + * Indicates whether the help command is for income by looking for the /i flag. + * + * @return true if the /i flag is present, false otherwise. + */ + private boolean hasIncomeFlag() { + return FLAG_OF_INCOME.equals(helpInput); + } + + /** + * Prints out the help page if the user requested for it. If not, it means an invalid command was received, + * and prints out an error message. + */ + public void printExpenditureHelpPage() { + if (isFromUser) { + String helpPage = "---------------------------------------Expenditure Help Page------------------------" + + "---------------\n" + + "1. Listing all Expenditures: list /e {DATE}\n" + + "2. Adding an Expenditure entry: add /e /pm [PAYMENT_METHOD] /c [CATEGORY] " + + "/d [DESCRIPTION] /a [AMOUNT] /t [DATE]\n" + + "3. Calculating the total expenditure in a month: calculate /epm [DATE]\n" + + "4. Updating an Expenditure: update /e [NEW_INDEX] /pm [NEW_PAYMENT_METHOD] /c [NEW_CATEGORY] " + + "/d [NEW_DESCRIPTION] /a [NEW_AMOUNT] /t [NEW_DATE]\n" + + "5. Removing an Expenditure entry: delete /e [INDEX]\n" + + "6. Exiting the program: bye\n" + + "----------------------------------------------------------------------------------------------" + + "-----\n"; + + System.out.println(helpPage); + } else { + System.out.println("Invalid command! \n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands\n"); + } + } + + /** + * Prints out the help page if the user requested for it. If not, it means an invalid command was received, + * and prints out an error message. + */ + public void printCreditCardHelpPage() { + String helpPage = "---------------------------------------Credit Card Help Page--------------------------" + + "-------------\n" + + "1. Listing all Credit Cards: list /cc\n" + + "2. Adding a Credit Card: add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CREDIT_LIMIT]\n" + + "3. Updating a Credit Card: update /cc [INDEX] /n [NEW_NAME] /cb [NEW_CASHBACK] " + + "/cl [NEW_CREDIT_LIMIT]\n" + + "4. Removing a credit card: delete /cc [INDEX]\n" + + "5. Exiting the program: bye\n" + + "-----------------------------------------------------------------------------------------------" + + "----\n"; + + System.out.println(helpPage); + } + + /** + * Prints out the Income help page. + */ + public void printIncomeHelpPage() { + String incomeHelpPage = "--------------------------------Income Help Page------------------------------" + + "---------\n" + + "1. Listing all Incomes: list /i\n" + + "2. Adding an Income entry: add /i /a [AMOUNT] /c [CATEGORY]\n" + + "3. Updating an Income entry: update /i [INDEX] /a [NEW_AMOUNT] /c [NEW_CATEGORY]\n" + + "4. Removing an Income entry: delete /i [INDEX]\n" + + "---------------------------------------------------------------------------------------\n"; + + System.out.println(incomeHelpPage); + } + + /** + * Prints either the Expenditure, Credit Card or Income help page based on the user's input. + * + * @throws MindMyMoneyException when an invalid command is received. + */ + public void executeCommand() throws MindMyMoneyException { + if (helpInput.equals(EMPTY_PARAMETER)) { + printExpenditureHelpPage(); + printCreditCardHelpPage(); + printIncomeHelpPage(); + } else if (hasExpensesFlag()) { + printExpenditureHelpPage(); + } else if (hasCreditCardListFlag()) { + printCreditCardHelpPage(); + } else if (hasIncomeFlag()) { + printIncomeHelpPage(); + } else { + throw new MindMyMoneyException("Please ensure that you have entered a valid help command.\n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/command/ListCommand.java b/src/main/java/seedu/mindmymoney/command/ListCommand.java new file mode 100644 index 0000000000..89200c2f99 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/ListCommand.java @@ -0,0 +1,262 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.constants.PrintStrings; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.helper.GeneralFunctions; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.Income; +import seedu.mindmymoney.userfinancial.User; + +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENSES; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_INCOME; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CREDIT_CARD; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; +import static seedu.mindmymoney.helper.TimeFunctions.isValidInputCalculateCommand; + +/** + * Represents the List command. + */ +public class ListCommand extends Command { + public ExpenditureList expenditureList; + public CreditCardList creditCardList; + public IncomeList incomeList; + private String listInput; + private static final int COUNTVALUE = 1; + + public ListCommand(String listInput, User user) { + this.expenditureList = user.getExpenditureListArray(); + this.creditCardList = user.getCreditCardListArray(); + this.incomeList = user.getIncomeListArray(); + this.listInput = listInput; + } + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return false; + } + + /** + * Indicates whether the list command is to list expenditure(s) by looking for the /e flag. + * + * @return true if the /pm flag is present, false otherwise. + */ + private boolean hasExpensesFlag() { + return listInput.startsWith(FLAG_OF_EXPENSES); + } + + /** + * Indicates whether the list command is to list credit card(s) by looking for the /cc flag. + * + * @return true if the /cc flag is present, false otherwise. + */ + private boolean hasCreditCardListFlag() { + return FLAG_OF_CREDIT_CARD.equals(listInput); + } + + /** + * Indicates whether the list command is to list income(s) by looking for the /i flag. + * + * @return true if the /i flag is present, false otherwise. + */ + private boolean hasIncomeListFlag() { + return FLAG_OF_INCOME.equals(listInput); + } + + /** + * Gets all expenditures and formats them into a String to be printed. + * + * @return String of expenditures. + * @throws MindMyMoneyException Throws an exception when the date is not in the correct format + */ + public String expenditureListToString() throws MindMyMoneyException { + try { + int count = COUNTVALUE; + String listInString = ""; + if (listInput.equals(FLAG_OF_EXPENSES)) { + listInString = listString(count, listInString); + } else { + listInString = outputListWithDate(count, listInString); + } + assert listInString.length() != 0 : "Return string should be non-empty"; + return listInString; + } catch (ArrayIndexOutOfBoundsException e) { + throw new MindMyMoneyException("Please ensure that you have entered a valid list command.\n" + + "Use 'list /e' to view your current list of expenditure\n" + + "Use 'list /cc' to view your current list of stored credit cards\n" + + "Use list /i to view your current list of incomes"); + } + } + + /** + * Outputs the list of expenses with date. + * + * @param count To obtain the numbering when listing the expenses. + * @param listInString String where the content of output is appended to. + * @return String of expenditures. + * @throws MindMyMoneyException Throws an exception when the date is not in the correct format. + */ + public String outputListWithDate(int count, String listInString) throws MindMyMoneyException { + String[] inputArray = GeneralFunctions.parseInput(listInput); + if (!inputArray[INDEX_OF_SECOND_ITEM].equals("")) { + if (!isValidInputCalculateCommand(inputArray[INDEX_OF_SECOND_ITEM])) { + throw new MindMyMoneyException("Date has to be valid and" + + " in \"dd/mm/yyyy\", \"mm/yyyy\" or \"yyyy\" format!"); + } + if (listStringWithDate(count, listInString, inputArray).equals("")) { + throw new MindMyMoneyException("Date not found in the list! Do check your input"); + } + return PrintStrings.LINE + listStringWithDate(count, listInString, inputArray) + PrintStrings.LINE; + } else { + return listString(count, listInString); + } + } + + /** + * Formats the output of expenses in list according to date. + * + * @param count To obtain the numbering when listing the expenses. + * @param listInString String where the content of output is appended to. + * @return String of expenditures + */ + public String listStringWithDate(int count, String listInString, String[] inputArray) { + for (Expenditure expenditure : expenditureList.expenditureListArray) { + if (expenditure.getTime().contains(inputArray[INDEX_OF_SECOND_ITEM])) { + listInString += count + ". $" + String.format("%.2f", expenditure.getAmount()) + " was spent on " + + expenditure.getDescription() + "(" + expenditure.getCategory() + ") " + "using " + + expenditure.getPaymentMethod() + " [" + expenditure.getTime() + "]" + "\n"; + count++; + } + } + return listInString; + } + + /** + * Formats the output of all expenses in list. + * + * @param count To obtain the numbering when listing the expenses. + * @param listInString String where the content of output is appended to. + * @return String of expenditures + */ + public String listString(int count, String listInString) { + listInString += PrintStrings.LINE; + for (Expenditure expenditure : expenditureList.expenditureListArray) { + listInString += count + ". $" + String.format("%.2f", expenditure.getAmount()) + " was spent on " + + expenditure.getDescription() + "(" + expenditure.getCategory() + ") " + "using " + + expenditure.getPaymentMethod() + " [" + expenditure.getTime() + "]" + "\n"; + count++; + } + listInString += PrintStrings.LINE; + return listInString; + } + + /** + * Prints user's current list of expenditures. + * + * @throws MindMyMoneyException when expenditure list is empty. + */ + public void printExpenditureList() throws MindMyMoneyException { + if (expenditureList.isEmpty()) { + throw new MindMyMoneyException( + "Your expenditure list is currently empty! Please add some expenditures to your list first"); + } else { + System.out.println(expenditureListToString()); + } + } + + /** + * Gets all credit cards and formats them into a String to be printed. + * + * @return String of credit cards. + */ + public String creditCardListToString() { + int indexOfList = 1; + String listInString = ""; + for (CreditCard creditCard : creditCardList.creditCardListArray) { + listInString += indexOfList + ". " + creditCard.toString(); + indexOfList++; + } + + assert listInString.length() != 0 : "Return string should be non-empty"; + return listInString; + } + + /** + * Prints user's current list of credit cards. + * + * @throws MindMyMoneyException when credit card list is empty. + */ + public void printCreditCardList() throws MindMyMoneyException { + if (creditCardList.isEmpty()) { + throw new MindMyMoneyException( + "Your credit card list is currently empty! Please add some credit cards to your account first"); + } else { + System.out.print(PrintStrings.LINE); + System.out.print(creditCardListToString()); + System.out.println(PrintStrings.LINE); + } + } + + /** + * Gets all incomes and formats them into a String to be printed. + * + * @return String of incomes. + */ + public String incomeListToString() { + int indexOfList = 1; + String listInString = ""; + for (Income income : incomeList.incomeListArray) { + listInString += indexOfList + ". " + income.toString(); + indexOfList++; + } + + assert listInString.length() != 0 : "Return string should be non-empty"; + return listInString; + } + + /** + * Prints user's current list of incomes. + * + * @throws MindMyMoneyException when income list is empty. + */ + public void printIncomeList() throws MindMyMoneyException { + if (incomeList.isEmpty()) { + throw new MindMyMoneyException("Your income list is currently empty! " + + "Please add some incomes to your account first"); + } else { + System.out.print(PrintStrings.LINE); + System.out.print(incomeListToString()); + System.out.println(PrintStrings.LINE); + } + } + + /** + * Prints either a list of expenditure(s), credit card(s) or income(s) based on the user's input. + * + * @throws MindMyMoneyException when an invalid command is received, along with its corresponding error message. + */ + @Override + public void executeCommand() throws MindMyMoneyException { + if (hasExpensesFlag()) { + printExpenditureList(); + } else if (hasCreditCardListFlag()) { + printCreditCardList(); + } else if (hasIncomeListFlag()) { + printIncomeList(); + } else { + throw new MindMyMoneyException("Please ensure that you have entered a valid list command.\n" + + "Use 'list /e' to view your current list of expenditure\n" + + "Use 'list /cc' to view your current list of stored credit cards\n" + + "Use list /i to view your current list of incomes"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/command/UpdateCommand.java b/src/main/java/seedu/mindmymoney/command/UpdateCommand.java new file mode 100644 index 0000000000..2bbc576b42 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/command/UpdateCommand.java @@ -0,0 +1,344 @@ +package seedu.mindmymoney.command; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.Income; +import seedu.mindmymoney.userfinancial.User; + +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CREDIT_CARD; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_INCOME; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CATEGORY; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_PAYMENT_METHOD; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_AMOUNT; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_TIME; +import static seedu.mindmymoney.constants.Flags.FLAG_END_VALUE; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CARD_LIMIT; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CARD_NAME; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_CASHBACK; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_DESCRIPTION; +import static seedu.mindmymoney.constants.Flags.FLAG_OF_EXPENSES; + +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; +import static seedu.mindmymoney.constants.Indexes.LIST_INDEX_CORRECTION; +import static seedu.mindmymoney.data.CreditCardList.isEqualName; +import static seedu.mindmymoney.data.CreditCardList.isEqualCashback; +import static seedu.mindmymoney.data.CreditCardList.isEqualCardLimit; +import static seedu.mindmymoney.data.ExpenditureList.isEqualCategory; +import static seedu.mindmymoney.data.ExpenditureList.isEqualPaymentMethod; +import static seedu.mindmymoney.data.ExpenditureList.isEqualDescription; +import static seedu.mindmymoney.data.ExpenditureList.isEqualAmount; +import static seedu.mindmymoney.data.ExpenditureList.isEqualTime; +import static seedu.mindmymoney.data.IncomeList.isEqualIncomeCategory; +import static seedu.mindmymoney.data.IncomeList.isEqualIncomeAmount; +import static seedu.mindmymoney.helper.AddCommandInputTests.testCreditCardParameters; +import static seedu.mindmymoney.helper.AddCommandInputTests.testUpdateExpenditureParameters; +import static seedu.mindmymoney.helper.AddCommandInputTests.testUpdateIncomeParameters; +import static seedu.mindmymoney.helper.GeneralFunctions.capitalise; +import static seedu.mindmymoney.helper.GeneralFunctions.parseInputWithCommandFlag; +import static seedu.mindmymoney.helper.GeneralFunctions.formatFloat; + +/** + * Represents the Update command. + */ +public class UpdateCommand extends Command { + private final String updateInput; + public ExpenditureList expenditureList; + public CreditCardList creditCardList; + public IncomeList incomeList; + + public UpdateCommand(String updateInput, User user) { + this.updateInput = updateInput; + this.expenditureList = user.getExpenditureListArray(); + this.creditCardList = user.getCreditCardListArray(); + this.incomeList = user.getIncomeListArray(); + } + + /** + * Indicates whether the program should exit. + * + * @return true if the program should exit, false otherwise. + */ + @Override + public boolean isExit() { + return false; + } + + /** + * Indicates whether the help command is for expenses by looking for the /e flag. + * + * @return true if the /e flag is present, false otherwise. + */ + private boolean hasExpensesFlag() { + return updateInput.startsWith(FLAG_OF_EXPENSES); + } + + /** + * Indicates whether the update command is to update a credit card by looking for the /cc flag. + * + * @return true if the /cc flag is present, false otherwise. + */ + private boolean hasCreditCardFlag() { + return updateInput.startsWith(FLAG_OF_CREDIT_CARD); + } + + /** + * Indicates whether the update command is to update an income by looking for the /i flag. + * + * @return true if the /i flag is present, false otherwise. + */ + private boolean hasIncomeFlag() { + return updateInput.startsWith(FLAG_OF_INCOME); + } + + /** + * Updates the total expenditure field in the credit card specified in the expenditure item, if the payment + * method is not Cash. + * + * @param newPaymentMethod Name of payment method to be updated. + * @param newExpenditureAmount Amount of new expenditure. + * @param expenditureIndex Index of expenditure to be updated + * @throws MindMyMoneyException when the payment method is not cash and is not found in user's credit card list. + */ + private void updatePaymentMethod(String newPaymentMethod, float newExpenditureAmount, int expenditureIndex) + throws MindMyMoneyException { + Expenditure oldExpenditure = expenditureList.get(expenditureIndex); + String oldPaymentMethod = oldExpenditure.getPaymentMethod(); + if (!oldPaymentMethod.equals("Cash")) { + CreditCard oldCreditCard = creditCardList.get(oldPaymentMethod); + oldCreditCard.deductExpenditure(oldExpenditure.getAmount()); + } + + if (!newPaymentMethod.equalsIgnoreCase("cash")) { + CreditCard newCreditCard = creditCardList.get(newPaymentMethod); + if (newCreditCard == null) { + throw new MindMyMoneyException("Please double-check your input! New payment method is not found!"); + } + newCreditCard.addExpenditure(newExpenditureAmount); + } + } + + /** + * Updates an Expenditure entry in user's expenditure list. + * + * @throws MindMyMoneyException when an invalid command is received. + */ + public void updateExpenditure() throws MindMyMoneyException { + try { + String[] parseUpdateInput = updateInput.split(" "); + String indexAsString = parseUpdateInput[INDEX_OF_SECOND_ITEM]; + final int indexToUpdate = Integer.parseInt(indexAsString) + LIST_INDEX_CORRECTION; + + String newPaymentMethod = + parseInputWithCommandFlag(updateInput, FLAG_OF_PAYMENT_METHOD, FLAG_OF_CATEGORY).trim(); + String inputCategory = parseInputWithCommandFlag(updateInput, FLAG_OF_CATEGORY, FLAG_OF_DESCRIPTION).trim(); + String newDescription = parseInputWithCommandFlag(updateInput, FLAG_OF_DESCRIPTION, FLAG_OF_AMOUNT).trim(); + String newAmountAsString = parseInputWithCommandFlag(updateInput, FLAG_OF_AMOUNT, FLAG_OF_TIME).trim(); + String inputTime = parseInputWithCommandFlag(updateInput, FLAG_OF_TIME, FLAG_END_VALUE).trim(); + + testUpdateExpenditureParameters(indexToUpdate, newPaymentMethod, inputCategory, newDescription, + newAmountAsString, inputTime, creditCardList, expenditureList); + + if (capitalise(newPaymentMethod).equals("Cash")) { + newPaymentMethod = capitalise(newPaymentMethod); + } + final String newCategory = capitalise(inputCategory); + float newAmountAsFloat = formatFloat(Float.parseFloat(newAmountAsString)); + + if (isSimilarExpenditure(indexToUpdate, newPaymentMethod, newCategory, newDescription, newAmountAsFloat, + inputTime)) { + throw new MindMyMoneyException("Expense fields to be updated is similar to the expense in the list.\n" + + "Please make sure the field descriptions you want to change are different."); + } + + updatePaymentMethod(newPaymentMethod, newAmountAsFloat, indexToUpdate); + + // Create new expenditure object to substitute in + Expenditure newExpenditure = new Expenditure(newPaymentMethod, newCategory, newDescription, + newAmountAsFloat, inputTime); + expenditureList.set(indexToUpdate, newExpenditure); + System.out.println("Successfully set expenditure " + indexAsString + " to:\n" + + "$" + String.format("%.2f", newExpenditure.getAmount()) + " was spent on " + + newExpenditure.getDescription() + + "(" + newExpenditure.getCategory() + ") " + "using " + newExpenditure.getPaymentMethod() + + " [" + newExpenditure.getTime() + "]"); + } catch (ArrayIndexOutOfBoundsException e) { + throw new MindMyMoneyException("Did you forget to input INDEX, DESCRIPTION or AMOUNT?"); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("AMOUNT and INDEX must be a number"); + } catch (IndexOutOfBoundsException e) { + throw new MindMyMoneyException("Please input a valid index"); + } + } + + /** + * Checks if the fields in the update command is similar to the fields in the expenditure in the list. + * + * @param index index of expenditure to update. + * @param newPaymentMethod new payment method field to be updated. + * @param newCategory new category field to be updated. + * @param newDescription new description field to be updated. + * @param newAmountAsFloat new amount field to be updated. + * @param newTime new time field to be updated. + * @return true if fields are similar, false otherwise. + */ + public boolean isSimilarExpenditure(int index, String newPaymentMethod, String newCategory, String newDescription, + float newAmountAsFloat, String newTime) { + if (isEqualPaymentMethod(expenditureList, index, newPaymentMethod) + && isEqualCategory(expenditureList, index, newCategory) + && isEqualDescription(expenditureList, index, newDescription) + && isEqualAmount(expenditureList, index, newAmountAsFloat) + && isEqualTime(expenditureList, index, newTime)) { + return true; + } + return false; + } + + /** + * Updates a Credit Card entry in user's credit card list. + * + * @throws MindMyMoneyException when an invalid command is received. + */ + public void updateCreditCard() throws MindMyMoneyException { + try { + String[] parseUpdateInput = updateInput.split(" "); + + // Get index to update + String indexAsString = parseUpdateInput[INDEX_OF_SECOND_ITEM]; + + // Parse data from input + String newCardName = parseInputWithCommandFlag(updateInput, FLAG_OF_CARD_NAME, + FLAG_OF_CASHBACK).trim(); + String newCashBack = parseInputWithCommandFlag(updateInput, FLAG_OF_CASHBACK, + FLAG_OF_CARD_LIMIT).trim(); + String newCardLimit = parseInputWithCommandFlag(updateInput, FLAG_OF_CARD_LIMIT, + + FLAG_END_VALUE).trim(); + testCreditCardParameters(newCardName, newCashBack, newCardLimit, creditCardList); + + int indexToUpdate = Integer.parseInt(indexAsString) + LIST_INDEX_CORRECTION; + float newCashBackAsDouble = formatFloat(Float.parseFloat(newCashBack)); + float newCardLimitAsFloat = formatFloat(Float.parseFloat(newCardLimit)); + CreditCard oldCreditCard = creditCardList.get(indexToUpdate); + if (oldCreditCard.getTotalExpenditure() > newCardLimitAsFloat) { + throw new MindMyMoneyException("Current spending has already exceeded the new limit!"); + } + if (isSimilarCreditCard(indexToUpdate, newCardName, newCashBackAsDouble, newCardLimitAsFloat)) { + throw new MindMyMoneyException("Credit Card fields to be updated is similar to the credit card in " + + "the list.\n" + "Please make sure the field descriptions you want to change are different."); + } + CreditCard newCreditCard = new CreditCard(newCardName, newCashBackAsDouble, + newCardLimitAsFloat); + + creditCardList.set(indexToUpdate, newCreditCard); + System.out.println("Successfully set credit card " + indexAsString + " to:\n" + + newCreditCard); + } catch (ArrayIndexOutOfBoundsException e) { + throw new MindMyMoneyException("Did you forget to input INDEX, NAME, CASHBACK or CREDIT LIMIT?"); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("INDEX, CASHBACK and CREDIT LIMIT must be a number"); + } catch (IndexOutOfBoundsException e) { + throw new MindMyMoneyException("Please input a valid index"); + } + } + + /** + * Checks if the fields in the update command is similar to the fields in the credit card in the list. + * + * @param index index of credit card to update. + * @param newCardName new card name field to be updated. + * @param newCashback new cash back field to be updated. + * @param newCardLimit new card limit field to be updated. + * @return true if fields are similar, false otherwise. + */ + public boolean isSimilarCreditCard(int index, String newCardName, double newCashback, float newCardLimit) { + if (isEqualName(creditCardList, index, newCardName) + && isEqualCashback(creditCardList, index, newCashback) + && isEqualCardLimit(creditCardList, index, newCardLimit)) { + return true; + } + return false; + } + + /** + * Updates an Income entry in user's income list. + * + * @throws MindMyMoneyException when an invalid command is received. + */ + public void updateIncome() throws MindMyMoneyException { + try { + String[] parseUpdateInput = updateInput.split(" "); + + String indexAsString = parseUpdateInput[INDEX_OF_SECOND_ITEM]; + int indexToUpdate = Integer.parseInt(indexAsString) + LIST_INDEX_CORRECTION; + + String newAmountAsString = parseInputWithCommandFlag(updateInput, FLAG_OF_AMOUNT, + FLAG_OF_CATEGORY).trim(); + int newAmountAsInt = Integer.parseInt(newAmountAsString); + + String inputCategory = parseInputWithCommandFlag(updateInput, FLAG_OF_CATEGORY, + FLAG_END_VALUE).trim(); + + testUpdateIncomeParameters(newAmountAsInt, inputCategory); + String newCategory = capitalise(inputCategory); + if (isSimilarIncome(indexToUpdate, newAmountAsInt, newCategory)) { + throw new MindMyMoneyException("Income fields to be updated is similar to the income in the list.\n" + + "Please make sure the field descriptions you want to change are different."); + } + Income newIncome = new Income(newAmountAsInt, newCategory); + incomeList.set(indexToUpdate, newIncome); + + System.out.print("Successfully set income " + indexAsString + " to:\n" + + "Amount: $" + newAmountAsString + "\n" + + "Category: " + newCategory + "\n" + + System.lineSeparator()); + + } catch (ArrayIndexOutOfBoundsException e) { + throw new MindMyMoneyException("Did you forget to input AMOUNT or CATEGORY?"); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("AMOUNT must be a number"); + } catch (IndexOutOfBoundsException e) { + throw new MindMyMoneyException("Please input a valid index"); + } + } + + /** + * Checks if the fields in the update command is similar to the fields in the income in the list. + * + * @param index index of income to update. + * @param newAmount new amount to be updated. + * @param newCategory new category to be updated. + * @return true if fields are similar, false otherwise. + */ + public boolean isSimilarIncome(int index, int newAmount, String newCategory) { + if (isEqualIncomeCategory(incomeList, index, newCategory) + && isEqualIncomeAmount(incomeList, index, newAmount)) { + return true; + } + return false; + } + + /** + * Updates either an Expenditure, Credit Card or Income entry based on the user's input. + * + * @throws MindMyMoneyException when an invalid command is received, along with its corresponding error message. + */ + @Override + public void executeCommand() throws MindMyMoneyException { + if (hasExpensesFlag()) { + updateExpenditure(); + } else if (hasCreditCardFlag()) { + updateCreditCard(); + } else if (hasIncomeFlag()) { + updateIncome(); + } else { + throw new MindMyMoneyException("You are missing a flag in your command\n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands"); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/constants/CalculationConversion.java b/src/main/java/seedu/mindmymoney/constants/CalculationConversion.java new file mode 100644 index 0000000000..2c79ab4f08 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/CalculationConversion.java @@ -0,0 +1,8 @@ +package seedu.mindmymoney.constants; + +/** + * Container for numbers used in calculations. + */ +public class CalculationConversion { + public static final float FLOAT_TO_PERCENTAGE = (float) 0.01; +} diff --git a/src/main/java/seedu/mindmymoney/constants/ExpenditureCategoryTypes.java b/src/main/java/seedu/mindmymoney/constants/ExpenditureCategoryTypes.java new file mode 100644 index 0000000000..c61c5030ab --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/ExpenditureCategoryTypes.java @@ -0,0 +1,13 @@ +package seedu.mindmymoney.constants; + +/** + * Container for Expenditure Category types. + */ +public enum ExpenditureCategoryTypes { + FOOD, + TRANSPORT, + UTILITIES, + PERSONAL, + ENTERTAINMENT, + OTHERS +} diff --git a/src/main/java/seedu/mindmymoney/constants/ExpenditureFields.java b/src/main/java/seedu/mindmymoney/constants/ExpenditureFields.java new file mode 100644 index 0000000000..31655875e2 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/ExpenditureFields.java @@ -0,0 +1,12 @@ +package seedu.mindmymoney.constants; + +/** + * Container for fields in expenditure. + */ +public enum ExpenditureFields { + EXPENDITURE, + CATEGORY, + DESCRIPTION, + AMOUNT, + TIME, +} diff --git a/src/main/java/seedu/mindmymoney/constants/Flags.java b/src/main/java/seedu/mindmymoney/constants/Flags.java new file mode 100644 index 0000000000..9ea6ce22f1 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/Flags.java @@ -0,0 +1,21 @@ +package seedu.mindmymoney.constants; + +/** + * Container for flags used in commands. + */ +public class Flags { + public static final String FLAG_OF_PAYMENT_METHOD = "/pm"; + public static final String FLAG_OF_CATEGORY = "/c"; + public static final String FLAG_OF_DESCRIPTION = "/d"; + public static final String FLAG_OF_AMOUNT = "/a"; + public static final String FLAG_OF_TIME = "/t"; + public static final String FLAG_END_VALUE = ""; + public static final String FLAG_OF_EXPENDITURE_PER_MONTH = "/epm"; + public static final String FLAG_OF_CARD_NAME = "/n"; + public static final String FLAG_OF_CASHBACK = "/cb"; + public static final String FLAG_OF_CARD_LIMIT = "/cl"; + public static final String FLAG_OF_CREDIT_CARD = "/cc"; + public static final String FLAG_OF_EXPENSES = "/e"; + public static final String FLAG_OF_INCOME = "/i"; + public static final String EMPTY_PARAMETER = ""; +} diff --git a/src/main/java/seedu/mindmymoney/constants/IncomeCategoryTypes.java b/src/main/java/seedu/mindmymoney/constants/IncomeCategoryTypes.java new file mode 100644 index 0000000000..4592cf6b0a --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/IncomeCategoryTypes.java @@ -0,0 +1,11 @@ +package seedu.mindmymoney.constants; + +/** + * Container for Income Category types. + */ +public enum IncomeCategoryTypes { + SALARY, + ALLOWANCE, + INVESTMENT, + OTHERS +} diff --git a/src/main/java/seedu/mindmymoney/constants/Indexes.java b/src/main/java/seedu/mindmymoney/constants/Indexes.java new file mode 100644 index 0000000000..ea98ce2d75 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/Indexes.java @@ -0,0 +1,22 @@ +package seedu.mindmymoney.constants; + +/** + * Container for indexes used by handlers. + */ +public class Indexes { + public static final int INDEX_OF_FIRST_ITEM = 0; + public static final int INDEX_OF_SECOND_ITEM = 1; + public static final int INDEX_OF_THIRD_ITEM = 2; + public static final int SPLIT_LIMIT = 2; + public static final int LIST_INDEX_CORRECTION = -1; + public static final int MINIMUM_EXPENDITURE_COMMAND_LENGTH = 3; + public static final int MINIMUM_CREDIT_CARD_COMMAND_LENGTH = 3; + public static final int MINIMUM_INCOME_COMMAND_LENGTH = 3; + public static final int MIN_STUDENT_INCOME = 0; + public static final int MAX_STUDENT_INCOME = 1000000; + public static final int MIN_EXPENDITURE_AMOUNT = 0; + public static final int MIN_CASHBACK_AMOUNT = 0; + public static final int MAX_CASHBACK_AMOUNT = 100; + public static final int MAX_EXPENDITURE_AMOUNT = 1000000; + public static final int MAX_CREDIT_CARD_LIMIT = 40000; +} diff --git a/src/main/java/seedu/mindmymoney/constants/PaymentMethod.java b/src/main/java/seedu/mindmymoney/constants/PaymentMethod.java new file mode 100644 index 0000000000..de01a65873 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/PaymentMethod.java @@ -0,0 +1,8 @@ +package seedu.mindmymoney.constants; + +/** + * Container for expenditure types. + */ +public enum PaymentMethod { + CASH, +} diff --git a/src/main/java/seedu/mindmymoney/constants/PrintStrings.java b/src/main/java/seedu/mindmymoney/constants/PrintStrings.java new file mode 100644 index 0000000000..b3120d6deb --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/PrintStrings.java @@ -0,0 +1,62 @@ +package seedu.mindmymoney.constants; + +/** + * Container for strings to be printed. + */ +public abstract class PrintStrings { + private PrintStrings() { + } + + public static final String LINE = "----------------------" + + "-------------------------" + System.lineSeparator(); + + public static final String[] tips = new String[] {"Set quantifiable financial goals", + "Split your income into different bank accounts, such as a Savings and Expenses account", + "Spend no more than 50% of your income on necessities", + "Only spend a maximum of 20% of your income on desires", + "At least 30% of your income should go into long-term savings and investments", + "Your emergency savings should be worth 3 to 6 months of your monthly salary", + "Your emergency funds should be relatively liquid", + "Understand your monthly cash flow and track how much you are spending", + "Put your emergency funds into high-interest savings accounts", + "Pay yourself first. Set aside a fixed amount for savings, before factoring other costs", + "Credit cards come with perks, but also high interest charges if you miss your payment", + "Credit cards are convenient, and paying them off on time helps build a good credit score", + "Banks will be more keen on lending money if you have a good credit score", + "Set a budget and stick to it", + "Don't make impulse purchases. Wait a week before deciding", + "Be frugal, not cheap", + "Discuss finances with your significant other", + "It is never too early to plan for retirement", + "First step of retirement planning is to determine how much money is needed at your desired age", + "Don't live beyond your means, especially to keep up appearances on social media", + "Start to read and learn more about personal finance", + "Check your bank accounts regularly to detect any unexpected charges", + "Be aware of your bank's minimum balance charges", + "Track your net worth, which can help visualize your progress towards your financial goals", + "It is often a better idea to buy a higher quality product even if it costs more, as it lasts longer", + "You can have too much savings. Start considering about investing", + "Invest in yourself. Stay updated on industry developments and continuously upgrade your skills", + "Optimize your earnings. Look out for side income opportunities and make smart career moves", + "Consider buying medical insurance, to enable you to make claims for treatment", + "If you have dependents such as kids or aged parents, life insurance is a must", + "Life insurance pays a lump sum upon death or permanent disability", + "Critical illness insurance pays a lump sum upon diagnosis of a critical illness", + "Must have insurance plans include a Shield Plan, Critical Illness and Life Insurance", + "Insurance is for protection, not investments. Get sufficient coverage for the lowest cost", + "In general, you should not spend more than 10% of your income on insurance premiums", + "The older you are, the more expensive insurance becomes", + "Make use of compound interest and start investing early", + "Time in market is better than timing the market", + "Dollar cost averaging refers to investing small amounts at regular intervals", + "Dollar cost averaging maximises your chances of paying a low average price over time", + "Investing for retirement and financial goals is better than just saving", + "Only invest in what you know. Investing always comes with risk", + "Having a diversified investment portfolio helps reduce risk", + "Investment-linked policies are policies that have insurance coverage and investment elements", + "Investment-linked policies may be an expensive way to invest", + "Investment-linked policies may not provide adequate insurance coverage", + "Pay attention to additional fees when investing", + "Invest in assets, and avoid buying liabilities", + "You can start investing as low as $100 per month through a Regular Savings Plan (RSP)"}; +} diff --git a/src/main/java/seedu/mindmymoney/constants/ValidationRegexTypes.java b/src/main/java/seedu/mindmymoney/constants/ValidationRegexTypes.java new file mode 100644 index 0000000000..968ce60100 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/constants/ValidationRegexTypes.java @@ -0,0 +1,11 @@ +package seedu.mindmymoney.constants; + +/** + * Container for validation regex types used in commands. + */ +public class ValidationRegexTypes { + public static final String VALIDATION_REGEX_D = + "^([0][1-9]|[12][0-9]|3[01])/([0][1-9]|1[012])/([0-9][0-9][0-9][0-9])$"; + public static final String VALIDATION_REGEX_M = "^([0][1-9]|1[012])/([0-9][0-9][0-9][0-9])$"; + public static final String VALIDATION_REGEX_Y = "^([0-9][0-9][0-9][0-9])$"; +} diff --git a/src/main/java/seedu/mindmymoney/data/CreditCardList.java b/src/main/java/seedu/mindmymoney/data/CreditCardList.java new file mode 100644 index 0000000000..b4bdee2fa3 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/data/CreditCardList.java @@ -0,0 +1,164 @@ +package seedu.mindmymoney.data; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.userfinancial.CreditCard; + +import java.util.ArrayList; +import java.util.Scanner; + +import static seedu.mindmymoney.helper.SerializerFunctions.SERIALIZATION_CREDIT_CARD_END_MARKER; +import static seedu.mindmymoney.helper.SerializerFunctions.SERIALIZATION_CREDIT_CARD_START_MARKER; +import static seedu.mindmymoney.helper.SerializerFunctions.addListToStringBuilder; +import static seedu.mindmymoney.helper.SerializerFunctions.convertInputToList; + + +/** + * Container for Credit Cards. + */ +public class CreditCardList { + public ArrayList creditCardListArray; + + public CreditCardList() { + this(new ArrayList<>()); + } + + public CreditCardList(ArrayList listArray) { + this.creditCardListArray = listArray; + } + + /** + * Checks if the list is empty. + * + * @return true if list is empty, false otherwise. + */ + public boolean isEmpty() { + return creditCardListArray.isEmpty(); + } + + /** + * Represents the size of the CreditCard list. + * + * @return size of the task list. + */ + public int size() { + return creditCardListArray.size(); + } + + /** + * Retrieves the CreditCard entry at the given index. + * + * @param index Index of the CreditCard entry. + * @return The CreditCard object. + */ + public CreditCard get(int index) { + return creditCardListArray.get(index); + } + + + /** + * Retrieves the CreditCard with the given name. + * + * @param name name of credit card to be searched. + * @return CreditCard object with matching name as parameter. + */ + public CreditCard get(String name) { + for (CreditCard creditCard : creditCardListArray) { + if (creditCard.getNameOfCard().equalsIgnoreCase(name.toLowerCase())) { + return creditCard; + } + } + return null; + } + + /** + * Deletes the CreditCard entry from the list. + * + * @param index Index of the CreditCard entry to delete. + */ + public void delete(int index) { + creditCardListArray.remove(index); + } + + /** + * Adds an CreditCard item to the list. + * + * @param item The CreditCard item to be added. + */ + public void add(CreditCard item) { + creditCardListArray.add(item); + } + + /** + * Updates the credit card entry at the given index. + * + * @param index Index of the entry to be updated. + * @param creditCard The new CreditCard entry. + */ + public void set(int index, CreditCard creditCard) { + creditCardListArray.set(index, creditCard); + } + + /** + * Checks if card name is equal. + * + * @param creditCardList List of credit card details. + * @param index Index of credit card list item. + * @param name Card name to compare with. + * @return True if card name is equal, false otherwise. + */ + public static boolean isEqualName(CreditCardList creditCardList, int index, String name) { + return creditCardList.get(index).getNameOfCard().equals(name); + } + + /** + * Checks if cashback is equal. + * + * @param creditCardList List of credit card details. + * @param index Index of credit card list item. + * @param cashback Cashback to compare with. + * @return True if cashback is equal, false otherwise. + */ + public static boolean isEqualCashback(CreditCardList creditCardList, int index, double cashback) { + return creditCardList.get(index).getCashback() == cashback; + } + + /** + * Checks if card limit is equal. + * + * @param creditCardList List of credit card details. + * @param index Index of credit card list item. + * @param cardLimit Card limit to compare with. + * @return True if card limit is equal, false otherwise. + */ + public static boolean isEqualCardLimit(CreditCardList creditCardList, int index, float cardLimit) { + return creditCardList.get(index).getMonthlyCardLimit() == cardLimit; + } + + /** + * Converts this CreditCardList into a machine-readable format. + * @return The serialized CreditCardList + */ + public String serialize() { + StringBuilder sb = new StringBuilder(); + addListToStringBuilder(SERIALIZATION_CREDIT_CARD_START_MARKER, + SERIALIZATION_CREDIT_CARD_END_MARKER, + creditCardListArray, + sb); + return sb.toString(); + } + + /** + * Reads a serialized CreditCardList from the scanner. + * @param scanner A scanner + * @returns A CreditCardList + * @throws MindMyMoneyException if the format is invalid. + */ + public static CreditCardList deserializeFrom(Scanner scanner) throws MindMyMoneyException { + CreditCardList creditCardList = new CreditCardList(); + creditCardList.creditCardListArray = convertInputToList( + SERIALIZATION_CREDIT_CARD_START_MARKER, + SERIALIZATION_CREDIT_CARD_END_MARKER, + scanner, CreditCard::deserialize); + return creditCardList; + } +} diff --git a/src/main/java/seedu/mindmymoney/data/ExpenditureList.java b/src/main/java/seedu/mindmymoney/data/ExpenditureList.java new file mode 100644 index 0000000000..72ee05e6b1 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/data/ExpenditureList.java @@ -0,0 +1,166 @@ +package seedu.mindmymoney.data; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.userfinancial.Expenditure; + +import java.util.ArrayList; +import java.util.Scanner; + +import static seedu.mindmymoney.helper.SerializerFunctions.SERIALIZATION_EXPENDITURE_END_MARKER; +import static seedu.mindmymoney.helper.SerializerFunctions.SERIALIZATION_EXPENDITURE_START_MARKER; +import static seedu.mindmymoney.helper.SerializerFunctions.addListToStringBuilder; +import static seedu.mindmymoney.helper.SerializerFunctions.convertInputToList; + +/** + * Container for expenditure lists. + */ +public class ExpenditureList { + public ArrayList expenditureListArray; + + public ExpenditureList() { + this(new ArrayList<>()); + } + + public ExpenditureList(ArrayList listArray) { + this.expenditureListArray = listArray; + } + + /** + * Checks if the list is empty. + * + * @return true if list is empty, false otherwise + */ + public boolean isEmpty() { + return expenditureListArray.isEmpty(); + } + + /** + * Represents the size of the task list. + * + * @return size of the task list. + */ + public int size() { + return expenditureListArray.size(); + } + + /** + * Retrieves the Expenditure entry at the given index. + * + * @param index Index of the Expenditure entry. + * @return The Expenditure object. + */ + public Expenditure get(int index) { + return expenditureListArray.get(index); + } + + /** + * Deletes the Expenditure entry from the list. + * + * @param index Index of the Expenditure entry to delete. + */ + public void delete(int index) { + expenditureListArray.remove(index); + } + + /** + * Adds an Expenditure entry to the list. + * + * @param item The Expenditure entry to be added. + */ + public void add(Expenditure item) { + expenditureListArray.add(item); + } + + /** + * Updates the Expenditure entry at the given index. + * + * @param index Index of the entry to be updated. + * @param item The new Expenditure entry. + */ + public void set(int index, Expenditure item) { + expenditureListArray.set(index, item); + } + + /** + * Checks if payment method is equal. + * @param expenditureList List of expenditures. + * @param index Index of expenditure in list. + * @param paymentMethod Payment method to compare with. + * @return True if payment method is equal, false otherwise. + */ + public static boolean isEqualPaymentMethod(ExpenditureList expenditureList, int index, String paymentMethod) { + return expenditureList.get(index).getPaymentMethod().equals(paymentMethod); + } + + /** + * Checks if category is equal. + * @param expenditureList List of expenditures details. + * @param index Index of expenditure in list. + * @param category Category to compare with. + * @return True if category is equal, false otherwise. + */ + public static boolean isEqualCategory(ExpenditureList expenditureList, int index, String category) { + return expenditureList.get(index).getCategory().equals(category); + } + + /** + * Checks if description is equal. + * @param expenditureList List of expenditures details. + * @param index Index of expenditure in list. + * @param description Description to compare with. + * @return True if description is equal, false otherwise. + */ + public static boolean isEqualDescription(ExpenditureList expenditureList, int index, String description) { + return expenditureList.get(index).getDescription().equals(description); + } + + /** + * Checks if amount is equal. + * @param expenditureList List of expenditures details. + * @param index Index of expenditure in list. + * @param amount Amount to compare with. + * @return True if amount is equal, false otherwise. + */ + public static boolean isEqualAmount(ExpenditureList expenditureList, int index, float amount) { + return expenditureList.get(index).getAmount() == amount; + } + + /** + * Checks if time is equal. + * @param expenditureList List of expenditures details. + * @param index Index of expenditure in list. + * @param time Time to compare with. + * @return True if time is equal, false otherwise. + */ + public static boolean isEqualTime(ExpenditureList expenditureList, int index, String time) { + return expenditureList.get(index).getTime().equals(time); + } + + /** + * Converts this ExpenditureList into a machine-readable format. + * @return The serialized ExpenditureList + */ + public String serialize() { + StringBuilder sb = new StringBuilder(); + addListToStringBuilder(SERIALIZATION_EXPENDITURE_START_MARKER, + SERIALIZATION_EXPENDITURE_END_MARKER, + expenditureListArray, + sb); + return sb.toString(); + } + + /** + * Reads a serialized ExpenditureList from the scanner. + * @param scanner A scanner + * @returns An ExpenditureList. + * @throws MindMyMoneyException if the format is invalid. + */ + public static ExpenditureList deserializeFrom(Scanner scanner) throws MindMyMoneyException { + ExpenditureList savedExpenditureList = new ExpenditureList(); + savedExpenditureList.expenditureListArray = convertInputToList( + SERIALIZATION_EXPENDITURE_START_MARKER, + SERIALIZATION_EXPENDITURE_END_MARKER, + scanner, Expenditure::deserialize); + return savedExpenditureList; + } +} diff --git a/src/main/java/seedu/mindmymoney/data/IncomeList.java b/src/main/java/seedu/mindmymoney/data/IncomeList.java new file mode 100644 index 0000000000..4c4228f8ed --- /dev/null +++ b/src/main/java/seedu/mindmymoney/data/IncomeList.java @@ -0,0 +1,124 @@ +package seedu.mindmymoney.data; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.userfinancial.Income; + +import java.util.ArrayList; +import java.util.Scanner; + +import static seedu.mindmymoney.helper.SerializerFunctions.SERIALIZATION_INCOME_END_MARKER; +import static seedu.mindmymoney.helper.SerializerFunctions.SERIALIZATION_INCOME_START_MARKER; +import static seedu.mindmymoney.helper.SerializerFunctions.addListToStringBuilder; +import static seedu.mindmymoney.helper.SerializerFunctions.convertInputToList; + +/** + * Container for income lists. + */ +public class IncomeList { + public ArrayList incomeListArray; + + public IncomeList() { + this(new ArrayList<>()); + } + + public IncomeList(ArrayList incomeListArray) { + this.incomeListArray = incomeListArray; + } + + /** + * Adds an Income entry to the list. + * + * @param income The Income entry to be added. + */ + public void add(Income income) { + incomeListArray.add(income); + } + + /** + * Checks if the list is empty. + * + * @return true if list is empty, false otherwise + */ + public boolean isEmpty() { + return incomeListArray.isEmpty(); + } + + /** + * Retrieves the Income entry at the given index. + * + * @param index Index of the income entry. + * @return The Income object. + */ + public Income get(int index) { + return incomeListArray.get(index); + } + + /** + * Updates the Income entry at the given index. + * + * @param index Index of the entry to be updated. + * @param income The new Income entry. + */ + public void set(int index, Income income) { + incomeListArray.set(index, income); + } + + /** + * Deletes the Income entry from the list. + * + * @param index Index of the Income entry to delete. + */ + public void delete(int index) { + incomeListArray.remove(index); + } + + /** + * Checks if income amount is equal. + * @param incomeList List of income details. + * @param index Index of income detail in list. + * @param amount Amount to compare with. + * @return True if amount is equal, false otherwise. + */ + public static boolean isEqualIncomeAmount(IncomeList incomeList, int index, int amount) { + return incomeList.get(index).getAmount() == amount; + } + + /** + * Checks if income category is equal. + * @param incomeList List of income details. + * @param index Index of income detail in list. + * @param category Category to compare with. + * @return True if category is equal, false otherwise. + */ + public static boolean isEqualIncomeCategory(IncomeList incomeList, int index, String category) { + return incomeList.get(index).getCategory().equals(category); + } + + /** + * Converts this IncomeList into a machine-readable format. + * @return The serialized IncomeList + */ + public String serialize() { + StringBuilder sb = new StringBuilder(); + addListToStringBuilder(SERIALIZATION_INCOME_START_MARKER, + SERIALIZATION_INCOME_END_MARKER, + incomeListArray, + sb); + return sb.toString(); + } + + /** + * Reads a serialized IncomeList from the scanner. + * @param scanner A scanner + * @returns An IncomeList + * @throws MindMyMoneyException if the format is invalid. + */ + public static IncomeList deserializeFrom(Scanner scanner) throws MindMyMoneyException { + IncomeList incomeList = new IncomeList(); + incomeList.incomeListArray = convertInputToList( + SERIALIZATION_INCOME_START_MARKER, + SERIALIZATION_INCOME_END_MARKER, + scanner, Income::deserialize); + return incomeList; + } +} diff --git a/src/main/java/seedu/mindmymoney/data/PropertyList.java b/src/main/java/seedu/mindmymoney/data/PropertyList.java new file mode 100644 index 0000000000..dcc96469b9 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/data/PropertyList.java @@ -0,0 +1,177 @@ +package seedu.mindmymoney.data; + +import seedu.mindmymoney.MindMyMoneyException; + +import java.util.HashMap; + +/** Class for storing a list of key-value pairs. This list can be serialized + * to a string. */ +public class PropertyList { + private final HashMap properties; + + public PropertyList() { + properties = new HashMap(); + } + + /** + * Converts the PropertyList into a String using a machine-readable format. + * @return The serialized PropertyList + */ + public String serialize() { + StringBuilder sb = new StringBuilder(); + for (String key : properties.keySet()) { + String serializedKeyValue = String.format(" %s: %s ", + quoteString(key), quoteString(properties.get(key))); + sb.append(serializedKeyValue); + } + return sb.toString(); + } + + @Override + public String toString() { + return serialize(); + } + + /** + * Adds a key-value pair to the PropertyList. + * @param key The key + * @param value The value. + */ + public void addProperty(String key, String value) { + properties.put(key, value); + } + + /** + * Retrieves the value associated with the given property. If the property does not exist, + * throw a MindMyMoneyException whose message is the property. + * @param property The property whose value to retrieve. + * @return The value. + * @throws MindMyMoneyException if the property is not in the PropertyList. + */ + public String getValue(String property) throws MindMyMoneyException { + String value = properties.get(property); + if (value == null) { + throw new MindMyMoneyException(property); + } + return value; + } + + /** + * Replaces all double quotes and backslashes in a string with versions safe to use in + * a quoted string. + * @param string The string to process. + * @return The string, with quotes and backslashes escaped. + */ + private static String escapeQuotesInString(String string) { + return string.replace("\\", "\\\\").replace("\"", "\\\""); + } + + /** + * Quotes a string. This puts it between double quotes, while adding backslash characters to any + * double quotes and backslashes the string has. + * @param string The string to quote. + * @return A quoted string. + */ + private static String quoteString(String string) { + return String.format("\"%s\"", escapeQuotesInString(string)); + } + + /** + * Converts a quoted string into its original form. + * @param string The string to unquote. + * @return An unquoted string. + * @throws MindMyMoneyException if the format is invalid. + */ + private static String unquoteString(String string) throws MindMyMoneyException { + String unescaped = string.replace("\\\"", "\"") + .replace("\\\\", "\\"); + if (unescaped.length() < 2 || unescaped.charAt(0) != '\"' + || unescaped.charAt(unescaped.length() - 1) != '\"') { + throw new MindMyMoneyException("Invalid unquoted string: " + string); + } + return unescaped.substring(1, unescaped.length() - 1); + } + + /** + * Reads a quoted string from the given string, starting at the given position. + * @param string The string to read from. + * @param startPos The start position. + * @return A quoted string. + * @throws MindMyMoneyException if the format is invalid. + */ + private static String readQuotedString(String string, int startPos) throws MindMyMoneyException { + if (string.charAt(startPos) != '\"') { + throw new MindMyMoneyException(string.substring(startPos) + " does not start with quoted string"); + } + int endPos = startPos + 1; + while (endPos < string.length()) { + if (string.charAt(endPos) == '\\') { + endPos += 2; + continue; + } + endPos += 1; + if (string.charAt(endPos - 1) == '\"') { + break; + } + } + return string.substring(startPos, endPos); + } + + /** + * Parses the output of serialize. + * @param string A serialized PropertyList. + * @return A PropertyList containing the same key-value pairs. + * @throws MindMyMoneyException if the format is invalid. + */ + public static PropertyList deserialize(String string) throws MindMyMoneyException { + PropertyList plist = new PropertyList(); + int propertyStart = 0; + while (propertyStart < string.length()) { + propertyStart = consumeWhitespace(string, propertyStart); + String quotedKey = readQuotedString(string, propertyStart); + propertyStart += quotedKey.length(); + + propertyStart = consumeWhitespace(string, propertyStart); + if (propertyStart == string.length() || string.charAt(propertyStart) != ':') { + throw new MindMyMoneyException("Expecting to start with colon, got " + + string.substring(propertyStart)); + } + propertyStart++; + + propertyStart = consumeWhitespace(string, propertyStart); + String quotedValue = readQuotedString(string, propertyStart); + propertyStart += quotedValue.length(); + + propertyStart = consumeWhitespace(string, propertyStart); + + String rawKey = unquoteString(quotedKey); + String rawValue = unquoteString(quotedValue); + plist.addProperty(rawKey, rawValue); + } + return plist; + } + + /** + * Moves an index into a string forward, until that index does not point to whitespace. + * @param string The string + * @param propertyStart The index + * @return The index, after having been moved forward. + */ + private static int consumeWhitespace(String string, int propertyStart) { + while (isStartAtWhitespace(string, propertyStart)) { + propertyStart++; + } + return propertyStart; + } + + /** + * Checks if an index into a string points to whitespace. + * @param string The string + * @param propertyStart The index + * @return Whether or not the index is in range of the string, and produces a valid index. + */ + + private static boolean isStartAtWhitespace(String string, int propertyStart) { + return propertyStart < string.length() && string.charAt(propertyStart) == ' '; + } +} diff --git a/src/main/java/seedu/mindmymoney/helper/AddCommandInputTests.java b/src/main/java/seedu/mindmymoney/helper/AddCommandInputTests.java new file mode 100644 index 0000000000..45228cf08d --- /dev/null +++ b/src/main/java/seedu/mindmymoney/helper/AddCommandInputTests.java @@ -0,0 +1,405 @@ +package seedu.mindmymoney.helper; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.constants.ExpenditureCategoryTypes; +import seedu.mindmymoney.constants.IncomeCategoryTypes; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.userfinancial.CreditCard; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static seedu.mindmymoney.constants.Indexes.MAX_CASHBACK_AMOUNT; +import static seedu.mindmymoney.constants.Indexes.MAX_CREDIT_CARD_LIMIT; +import static seedu.mindmymoney.constants.Indexes.MAX_EXPENDITURE_AMOUNT; +import static seedu.mindmymoney.constants.Indexes.MAX_STUDENT_INCOME; +import static seedu.mindmymoney.constants.Indexes.MIN_CASHBACK_AMOUNT; +import static seedu.mindmymoney.constants.Indexes.MIN_EXPENDITURE_AMOUNT; +import static seedu.mindmymoney.constants.Indexes.MIN_STUDENT_INCOME; +import static seedu.mindmymoney.constants.PaymentMethod.CASH; +import static seedu.mindmymoney.helper.TimeFunctions.checkAfterCurrentDate; +import static seedu.mindmymoney.helper.TimeFunctions.checkValidDate; + +/** + * Input validation for Add Command. + */ +public class AddCommandInputTests { + + /** + * Checks if input is cash or a name of credit card. + * + * @param input the item to be checked. + * @return true if item is in the list, false otherwise. + */ + public static boolean isExpenditureInList(String input, CreditCardList creditCardList) { + if (input.equalsIgnoreCase(CASH.toString())) { + return true; + } + for (CreditCard str : creditCardList.creditCardListArray) { + if (str.getNameOfCard().equals(input)) { + return true; + } + } + return false; + } + + /** + * Checks if input is in Expenditure Category Types. + * + * @param input the item to be checked. + * @return true if item is in the list, false otherwise. + */ + public static boolean isExpenditureCategoryInList(String input) { + for (ExpenditureCategoryTypes str : ExpenditureCategoryTypes.values()) { + if (str.name().equalsIgnoreCase(input)) { + return true; + } + } + return false; + } + + /** + * Checks if input is in Income Category Types. + * + * @param input the item to be checked. + * @return true if item is in the list, false otherwise. + */ + public static boolean isIncomeCategoryInList(String input) { + for (IncomeCategoryTypes str : IncomeCategoryTypes.values()) { + if (str.name().equalsIgnoreCase(input)) { + return true; + } + } + return false; + } + + /** + * Checks if user input of expenditure is null or not a type of expenditure. + * + * @param inputPaymentMethod User input of Expenditure. + * @throws MindMyMoneyException when input is null or not a type of expenditure. + */ + public static void testPaymentMethod(String inputPaymentMethod, CreditCardList creditCardList) + throws MindMyMoneyException { + if (inputPaymentMethod.trim().equals("")) { + throw new MindMyMoneyException("Expenditure cannot be null!"); + } + + if (!isExpenditureInList(inputPaymentMethod, creditCardList)) { + throw new MindMyMoneyException("Input Cash or a Credit Card after the /pm field!"); + } + } + + /** + * Checks if user input of category is null or not a type of expenditure. + * + * @param inputCategory User input of Category. + * @throws MindMyMoneyException when input is null or not a type of category. + */ + public static void testExpenditureCategory(String inputCategory) throws MindMyMoneyException { + if (inputCategory.trim().equals("")) { + throw new MindMyMoneyException("Category cannot be null!"); + } + if (!isExpenditureCategoryInList(inputCategory)) { + throw new MindMyMoneyException("Input Food, Transport, Utilities, Personal, Entertainment or Others after" + + " the /c field!"); + } + } + + /** + * Checks if user input of category is null. + * + * @param inputDescription User input of Description. + * @throws MindMyMoneyException when input is null. + */ + public static void testDescription(String inputDescription) throws MindMyMoneyException { + if (inputDescription.trim().equals("")) { + throw new MindMyMoneyException("Description cannot be null!"); + } + } + + /** + * Checks if the expenditure amount is above the credit card limit or balance. + * + * @param inputAmountAsFloat The expenditure amount. + * @param paymentMethod Either as cash or as a credit card. + * @param creditCardList User's current list of credit cards. + * @return true if expenditure amount is over the card limit or balance, false otherwise. + */ + public static boolean isOverLimit(Float inputAmountAsFloat, String paymentMethod, CreditCardList creditCardList) { + if (paymentMethod.equalsIgnoreCase("cash")) { + return false; + } + + CreditCard creditcard = creditCardList.get(paymentMethod); + float balanceLeft = creditcard.getBalanceLeft(); + + if (inputAmountAsFloat > balanceLeft) { + return true; + } + return false; + } + + /** + * Checks if user input of amount is a positive number more than 0. + * + * @param inputAmount User input of Amount. + * @param paymentMethod User's payment method. + * @param creditCardList User's current list of credit cards + * @throws MindMyMoneyException when input is less than or equal to 0 or null. + */ + public static void testExpenditureAmount(String inputAmount, String paymentMethod, + CreditCardList creditCardList) throws MindMyMoneyException { + float inputAmountAsFloat; + + if (inputAmount == null) { + throw new MindMyMoneyException("Amount cannot be empty!"); + } + + try { + inputAmountAsFloat = Float.parseFloat(inputAmount); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Amount must be a number"); + } + + if (isOverLimit(inputAmountAsFloat, paymentMethod, creditCardList)) { + throw new MindMyMoneyException("You have exceeded your credit card limit!"); + } + + if (inputAmountAsFloat <= MIN_EXPENDITURE_AMOUNT) { + throw new MindMyMoneyException("Amount must be more than 0"); + } + if (inputAmountAsFloat > MAX_EXPENDITURE_AMOUNT) { + throw new MindMyMoneyException("Expenditure cannot be more than $1 million!"); + } + assert inputAmountAsFloat > 0 : "Amount should have a positive value"; + } + + public static void testIncomeAmount(int inputAmount) throws MindMyMoneyException { + if (inputAmount < MIN_STUDENT_INCOME) { + throw new MindMyMoneyException("Amount cannot be negative!"); + } else if (inputAmount > MAX_STUDENT_INCOME) { + throw new MindMyMoneyException("Amount too high!"); + } + } + + public static void testIncomeCategory(String inputCategory) throws MindMyMoneyException { + if (inputCategory.trim().equals("")) { + throw new MindMyMoneyException("Category cannot be null!"); + } + + if (!isIncomeCategoryInList(inputCategory)) { + throw new MindMyMoneyException("Input Salary, Allowance, Investment or Others!"); + } + } + + public static void testUpdateIncomeParameters(int inputAmount, String inputCategory) throws MindMyMoneyException { + testIncomeAmount(inputAmount); + testIncomeCategory(inputCategory); + } + + /** + * Checks if user input of credit card name is valid. + * Credit Card name that is empty, as "Cash" and that already exist in the list are not accepted. + * + * @throws MindMyMoneyException when Credit Card name is cash or has a + */ + public static void testCreditCardName(String inputCreditCardName, CreditCardList creditCardList) + throws MindMyMoneyException { + if (inputCreditCardName.equalsIgnoreCase("")) { + throw new MindMyMoneyException("Credit card name cannot be empty!"); + } + assert inputCreditCardName != null : "Credit Card name should not be empty."; + + if (inputCreditCardName.equalsIgnoreCase("cash")) { + throw new MindMyMoneyException("Credit card name cannot be abbreviated as `Cash`."); + } + for (CreditCard creditCard : creditCardList.creditCardListArray) { + if (creditCard.getNameOfCard().toLowerCase().equalsIgnoreCase(inputCreditCardName)) { + throw new MindMyMoneyException("You already have this card in the list! " + + "Please abbreviate the card as a different name."); + } + } + + } + + /** + * Checks if user input of cashback is a positive number more than 0. + * Cashback also cannot be more than 100%. + * + * @param inputCashback User input of Cash back. + * @throws MindMyMoneyException when input cashback is less than 0 or null. + */ + public static void testCashbackAmount(String inputCashback) throws MindMyMoneyException { + double inputAmountAsDouble; + if (inputCashback == null) { + throw new MindMyMoneyException("Cashback cannot be empty!"); + } + + try { + inputAmountAsDouble = Double.parseDouble(inputCashback); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Cashback must be a number"); + } + + if (inputAmountAsDouble < MIN_CASHBACK_AMOUNT) { + throw new MindMyMoneyException("Cashback must be more than or equals to 0"); + } else if (inputAmountAsDouble >= MAX_CASHBACK_AMOUNT) { + throw new MindMyMoneyException("Cashback cannot be 100% or more!"); + } + assert inputAmountAsDouble >= MIN_CASHBACK_AMOUNT : "Cashback should have a non-negative value"; + } + + /** + * Checks if user input for credit card limit is valid. + * + * @param inputLimit User input of credit card limit. + * @throws MindMyMoneyException when input credit card limit is less than or equal to 0 or null. + */ + public static void testCreditCardLimit(String inputLimit) throws MindMyMoneyException { + float inputAmountAsDouble; + if (inputLimit == null) { + throw new MindMyMoneyException("Limit amount cannot be empty!"); + } + + try { + inputAmountAsDouble = Float.parseFloat(inputLimit); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Limit amount must be a number"); + } + + if (inputAmountAsDouble <= 0) { + throw new MindMyMoneyException("Limit amount must be more than 0"); + } + assert inputAmountAsDouble > 0 : "Limit amount should have a positive value"; + + if (inputAmountAsDouble > MAX_CREDIT_CARD_LIMIT) { + throw new MindMyMoneyException("Limit amount must be $40,000 or less.\n" + + "If you do have a credit card with more than $40,000 limit, " + + "do inform the MindMyMoney team through GitHub."); + } + + } + + /** + * Tests if the input parameters of expenditure from the user are valid. + * + * @param paymentMethod The payment method used, either as cash or the credit card. + * @param inputCategory The category as indicated by the user. + * @param description The description of the expenditure. + * @param amountAsString Price of the expenditure. + * @param inputTime Date of when the expenditure was bought. + * @throws MindMyMoneyException when the parameters are invalid. + */ + public static void testExpenditureParameters(String paymentMethod, String inputCategory, String description, + String amountAsString, String inputTime, + CreditCardList creditCardList) throws MindMyMoneyException { + testPaymentMethod(paymentMethod, creditCardList); + testExpenditureCategory(inputCategory); + testDescription(description); + testExpenditureAmount(amountAsString, paymentMethod, creditCardList); + checkValidDate(inputTime); + LocalDate date = LocalDate.parse(inputTime, DateTimeFormatter.ofPattern("dd/MM/yyyy")); + checkAfterCurrentDate(date); + } + + /** + * Tests if the input parameters of update expenditure from the user are valid. + * + * @param newPaymentMethod The payment method used, either as cash or the credit card. + * @param inputCategory The category as indicated by the user. + * @param description The description of the expenditure. + * @param amountAsString Price of the expenditure. + * @param inputTime Date of when the expenditure was bought. + * @throws MindMyMoneyException when the parameters are invalid. + */ + public static void testUpdateExpenditureParameters(int indexToUpdate, String newPaymentMethod, String inputCategory, + String description, String amountAsString, String inputTime, + CreditCardList creditCardList, ExpenditureList expenditureList) + throws MindMyMoneyException { + testPaymentMethod(newPaymentMethod, creditCardList); + testExpenditureCategory(inputCategory); + testDescription(description); + checkValidDate(inputTime); + LocalDate date = LocalDate.parse(inputTime, DateTimeFormatter.ofPattern("dd/MM/yyyy")); + checkAfterCurrentDate(date); + + //Test updated expenditure amount + String oldPaymentMethod = getOldPaymentMethod(indexToUpdate, expenditureList); + if (isSamePaymentMethod(oldPaymentMethod, newPaymentMethod) + && !newPaymentMethod.equalsIgnoreCase("cash")) { + testSameCreditCardExpenditure(indexToUpdate, amountAsString, expenditureList, creditCardList, + newPaymentMethod); + } else { + testExpenditureAmount(amountAsString, newPaymentMethod, creditCardList); + } + } + + private static String getOldPaymentMethod(int indexToUpdate, ExpenditureList expenditureList) { + return expenditureList.get(indexToUpdate).getPaymentMethod(); + } + + private static boolean isSamePaymentMethod(String oldPaymentMethod, String newPaymentMethod) { + return oldPaymentMethod.equalsIgnoreCase(newPaymentMethod); + } + + private static void testSameCreditCardExpenditure(int indexToUpdate, String inputAmount, + ExpenditureList expenditureList, CreditCardList creditCardList, + String paymentMethod) + throws MindMyMoneyException { + + float inputAmountAsFloat; + if (inputAmount == null) { + throw new MindMyMoneyException("Amount cannot be empty!"); + } + try { + inputAmountAsFloat = Float.parseFloat(inputAmount); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Amount must be a number"); + } + + CreditCard creditCard = creditCardList.get(paymentMethod); + float oldExpenditureAmount = expenditureList.get(indexToUpdate).getAmount(); + float newTotalExpenditure = creditCard.getTotalExpenditure() - oldExpenditureAmount + + inputAmountAsFloat; + boolean isOverLimit = creditCard.getMonthlyCardLimit() < newTotalExpenditure; + + if (isOverLimit) { + throw new MindMyMoneyException("You have exceeded your credit card limit!"); + } + + if (inputAmountAsFloat <= MIN_EXPENDITURE_AMOUNT) { + throw new MindMyMoneyException("Amount must be more than 0"); + } + assert inputAmountAsFloat > 0 : "Amount should have a positive value"; + } + + /** + * Tests if the input parameters of income from the user are valid. + * + * @param amountAsInt Integer amount of the income. + * @param inputCategory Source of income. + * @throws MindMyMoneyException when the parameters are invalid. + */ + public static void testIncomeParameters(int amountAsInt, String inputCategory) throws MindMyMoneyException { + testIncomeAmount(amountAsInt); + testIncomeCategory(inputCategory); + } + + /** + * Tests if the input parameters of credit card from the user are valid. + * + * @param cardName The name of the credit card. + * @param cashBack The amount of cashback the card provides. + * @param cardLimit The spending limit of the credit card. + * @throws MindMyMoneyException when the parameters are invalid. + */ + public static void testCreditCardParameters(String cardName, String cashBack, String cardLimit, + CreditCardList creditCardList) throws MindMyMoneyException { + testCreditCardName(cardName, creditCardList); + testCashbackAmount(cashBack); + testCreditCardLimit(cardLimit); + } + +} diff --git a/src/main/java/seedu/mindmymoney/helper/Calculations.java b/src/main/java/seedu/mindmymoney/helper/Calculations.java new file mode 100644 index 0000000000..26676985f0 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/helper/Calculations.java @@ -0,0 +1,123 @@ +package seedu.mindmymoney.helper; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.constants.ExpenditureCategoryTypes; +import seedu.mindmymoney.constants.PrintStrings; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.userfinancial.Expenditure; + +import java.util.ArrayList; + +import static seedu.mindmymoney.constants.ExpenditureCategoryTypes.FOOD; +import static seedu.mindmymoney.constants.ExpenditureCategoryTypes.UTILITIES; +import static seedu.mindmymoney.constants.ExpenditureCategoryTypes.TRANSPORT; +import static seedu.mindmymoney.constants.ExpenditureCategoryTypes.PERSONAL; +import static seedu.mindmymoney.constants.ExpenditureCategoryTypes.ENTERTAINMENT; +import static seedu.mindmymoney.constants.ExpenditureCategoryTypes.OTHERS; +import static seedu.mindmymoney.constants.ExpenditureFields.TIME; +import static seedu.mindmymoney.helper.GeneralFunctions.findItemsInList; +import static seedu.mindmymoney.helper.GeneralFunctions.formatFloat; +import static seedu.mindmymoney.helper.GeneralFunctions.findMatchingCategoryInArraylist; +import static seedu.mindmymoney.helper.TimeFunctions.isValidInputCalculateCommand; + +/** + * Container for functions that help do calculations. + */ +public class Calculations { + public static final double INTERVAL_OF_INCREMENT = 5; + + /** + * Calculates the total expenditure in a given month. + * + * @param input The month to calculate expenditure for. + * @param expenditureList The list containing all expenditures to search for. + * @throws MindMyMoneyException When findItemsInList throws MindMyMoneyException. + */ + public static void calculateExpenditure(String input, ExpenditureList expenditureList) + throws MindMyMoneyException { + if (!isValidInputCalculateCommand(input)) { + throw new MindMyMoneyException("Date has to be in \"dd/mm/yyyy\", \"mm/yyyy\" or \"yyyy\" format!"); + } + ArrayList foundItems = findItemsInList(input, TIME.toString(), expenditureList); + float sumOfExpenditure = 0; + for (Expenditure item : foundItems) { + sumOfExpenditure += item.getAmount(); + } + sumOfExpenditure = formatFloat(sumOfExpenditure); + if (sumOfExpenditure == 0.0) { + throw new MindMyMoneyException("Date not found in the list! Do check your input"); + } + System.out.println("Total expenditure in " + input + " is $" + String.format("%.2f", sumOfExpenditure) + "."); + displayExpenditureBreakdown(foundItems, sumOfExpenditure); + } + + /** + * Displays the expenditure breakdown for the given month. + * + * @param foundItems The list containing all the expenses in the month. + * @param sumOfExpenditure Total sum of expenses in the month. + */ + public static void displayExpenditureBreakdown(ArrayList foundItems, float sumOfExpenditure) { + float foodPercentage = calculatePercentage(FOOD, foundItems, sumOfExpenditure); + float transportPercentage = calculatePercentage(TRANSPORT, foundItems, sumOfExpenditure); + float utilitiesPercentage = calculatePercentage(UTILITIES, foundItems, sumOfExpenditure); + float personalPercentage = calculatePercentage(PERSONAL, foundItems, sumOfExpenditure); + float entertainmentPercentage = calculatePercentage(ENTERTAINMENT, foundItems, sumOfExpenditure); + float othersPercentage = calculatePercentage(OTHERS, foundItems, sumOfExpenditure); + System.out.println(System.lineSeparator() + "BREAKDOWN OF EXPENSES:"); + System.out.print(PrintStrings.LINE); + System.out.println("FOOD: " + printOutput(foodPercentage)); + System.out.println("TRANSPORT: " + printOutput(transportPercentage)); + System.out.println("UTILITIES: " + printOutput(utilitiesPercentage)); + System.out.println("PERSONAL: " + printOutput(personalPercentage)); + System.out.println("ENTERTAINMENT: " + printOutput(entertainmentPercentage)); + System.out.println("OTHERS: " + printOutput(othersPercentage)); + System.out.println(PrintStrings.LINE); + } + + /** + * Prints the bar chart. + * + * @param percentage Percentage of each category type that is part of the expenses. + * @return The remaining part of string to be printed. + */ + public static String printOutput(float percentage) { + return printBar(percentage) + " [" + percentage + "%]"; + } + + /** + * Fills the chart for each category type. + * + * @param percentage Percentage of each category type that is part of the expenses. + * @return The 'bar' chart to be output for the category type. + */ + public static String printBar(float percentage) { + String output = ""; + for (float i = 0; i < percentage; i += INTERVAL_OF_INCREMENT) { + output += "$$"; + } + return output; + } + + /** + * Calculates the percentage of expenses for a particular category type. + * + * @param categoryType Category type to calculate for. + * @param foundItems The list containing all the expenses in the month. + * @param sumOfExpenditure Total sum of expenses in the month. + * @return Percentage of expenses for that particular category type. + */ + public static float calculatePercentage(ExpenditureCategoryTypes categoryType, ArrayList foundItems, + float sumOfExpenditure) { + ArrayList foundCategoryTypeItems = new ArrayList<>(); + foundCategoryTypeItems = findMatchingCategoryInArraylist(categoryType, foundItems, foundCategoryTypeItems); + float sumOfCategoryType = 0; + for (Expenditure item : foundCategoryTypeItems) { + sumOfCategoryType += item.getAmount(); + } + sumOfCategoryType = formatFloat(sumOfCategoryType); + float percentage = (sumOfCategoryType / sumOfExpenditure) * 100; + percentage = formatFloat(percentage); + return percentage; + } +} diff --git a/src/main/java/seedu/mindmymoney/helper/GeneralFunctions.java b/src/main/java/seedu/mindmymoney/helper/GeneralFunctions.java new file mode 100644 index 0000000000..f57886d59b --- /dev/null +++ b/src/main/java/seedu/mindmymoney/helper/GeneralFunctions.java @@ -0,0 +1,258 @@ +package seedu.mindmymoney.helper; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.constants.ExpenditureCategoryTypes; +import seedu.mindmymoney.constants.ExpenditureFields; +import seedu.mindmymoney.constants.Indexes; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.userfinancial.Expenditure; + +import java.text.DecimalFormat; +import java.util.ArrayList; + +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; + +/** + * Container for general functions used throughout the program. + */ +public class GeneralFunctions { + private static final DecimalFormat df = new DecimalFormat("0.00"); + + /** + * Separates the user input into the command and description for easy reference. + * + * @return String array of user input. + */ + public static String[] parseInput(String inputCommand) { + String[] inputAsArray = inputCommand.split(" ", Indexes.SPLIT_LIMIT); + return inputAsArray; + } + + /** + * Checks if user's input contains the correct flag formats. + * + * @param input user's input. + * @param startingFlag the flag before the interested parameter. + * @param endingFlag the flag after the interested parameter. + * @return true if the input contains the correct flag formats, false otherwise. + */ + public static boolean hasCorrectFlagFormat(String input, String startingFlag, String endingFlag) { + if (input.contains(startingFlag + " ") && input.contains(" " + endingFlag)) { + return true; + } + return false; + } + + /** + * Extracts out the interested parameter from the user's input based on the given flags. + * For example, if user's input was: "add /i /a 3000 /c salary", this method extracts out "3000" or "salary" + * based on the flags given. + * + * @param input user's input. + * @param startingFlag the flag before the interested parameter. + * @param endingFlag the flag after the interested parameter. + * @return the interested parameter. + * @throws MindMyMoneyException when an invalid command is received, along with the corresponding error message. + */ + public static String parseInputWithCommandFlag(String input, String startingFlag, String endingFlag) + throws MindMyMoneyException { + try { + if (!hasCorrectFlagFormat(input, startingFlag, endingFlag)) { + throw new MindMyMoneyException("You are missing a flag or lack the spacing between the flags!\n" + + "For eg. \"add /e /pm cash /c Food /d Porridge /a 4.50 /t 30/03/2022\""); + } + + startingFlag = startingFlag + " "; + input = input.substring(input.indexOf(startingFlag) + startingFlag.length()); + if (!endingFlag.equals("")) { + endingFlag = " " + endingFlag; + input = input.substring(0, input.indexOf(endingFlag)); + } + } catch (StringIndexOutOfBoundsException e) { + throw new MindMyMoneyException("You are missing one or more of the parameters! Please check your command " + + "again.\n"); + } + return input; + } + + /** + * Finds an item in a given list provided the search term and the field to search in. + * + * @param searchTerm The matching term to search the list for. + * @param fieldToSearch The object in the list to search for. + * @param itemList The list to search in. + * @return An ArrayList containing the found items. + * @throws MindMyMoneyException if fieldToSearch is not in the list, amount is not a number + * and if the list of found items is empty. + */ + public static ArrayList findItemsInList(String searchTerm, String fieldToSearch, + ExpenditureList itemList) throws MindMyMoneyException { + ArrayList foundItems = new ArrayList<>(); + try { + ExpenditureFields fieldToSearchAsEnumType = ExpenditureFields.valueOf(fieldToSearch); + switch (fieldToSearchAsEnumType) { + case EXPENDITURE: + findMatchingExpenditure(searchTerm, foundItems, itemList); + break; + case CATEGORY: + findMatchingCategory(searchTerm, foundItems, itemList); + break; + case DESCRIPTION: + findMatchingDescription(searchTerm, foundItems, itemList); + break; + case AMOUNT: + findMatchingAmount(searchTerm, foundItems, itemList); + break; + case TIME: + findMatchingTime(searchTerm, foundItems, itemList); + break; + default: + throw new MindMyMoneyException("Search term that have yet to be implemented! " + + "Look out for our future updates"); + } + if (foundItems.size() == 0) { + throw new MindMyMoneyException("The item \"" + searchTerm + "\" was not found in the list, sorry!"); + } else { + return foundItems; + } + } catch (IllegalArgumentException e) { + throw new MindMyMoneyException("Input a valid search term!"); + } + } + + /** + * Searches for matching items in Expenditure field of itemList and returns a list of found items. + * + * @param searchTerm String to search for. + * @param foundItems List to store items found. + * @param itemList List to search in. + * @return foundItems, updated with new items found. + */ + public static ArrayList findMatchingExpenditure(String searchTerm, ArrayList foundItems, + ExpenditureList itemList) { + for (Expenditure item : itemList.expenditureListArray) { + if (item.getPaymentMethod().contains(searchTerm)) { + foundItems.add(item); + } + } + return foundItems; + } + + /** + * Searches for matching items in Category field of itemList and returns a list of found items. + * + * @param searchTerm String to search for. + * @param foundItems List to store items found. + * @param itemList List to search in. + * @return foundItems, updated with new items found. + */ + public static ArrayList findMatchingCategory(String searchTerm, ArrayList foundItems, + ExpenditureList itemList) { + for (Expenditure item : itemList.expenditureListArray) { + if (item.getCategory().contains(searchTerm)) { + foundItems.add(item); + } + } + return foundItems; + } + + /** + * Searches for matching items in Category field from an arraylist and returns a list of found items. + * + * @param categoryType Category type to search for + * @param foundItems List of expense items. + * @param foundCategoryTypeList List to store items found. + * @return The list that stores the expense items found. + */ + public static ArrayList findMatchingCategoryInArraylist(ExpenditureCategoryTypes categoryType, + ArrayList foundItems, + ArrayList foundCategoryTypeList) { + for (Expenditure item : foundItems) { + if (item.getCategory().contains(capitalise(categoryType.toString()))) { + foundCategoryTypeList.add(item); + } + } + return foundCategoryTypeList; + } + + /** + * Searches for matching items in Description field of itemList and returns a list of found items. + * + * @param searchTerm String to search for. + * @param foundItems List to store items found. + * @param itemList List to search in. + * @return foundItems, updated with new items found. + */ + public static ArrayList findMatchingDescription(String searchTerm, ArrayList foundItems, + ExpenditureList itemList) { + for (Expenditure item : itemList.expenditureListArray) { + if (item.getDescription().contains(searchTerm)) { + foundItems.add(item); + } + } + return foundItems; + } + + /** + * Searches for matching items in Amount field of itemList and returns a list of found items. + * + * @param searchTerm String to search for. + * @param foundItems List to store items found. + * @param itemList List to search in. + * @return foundItems, updated with new items found. + */ + public static ArrayList findMatchingAmount(String searchTerm, ArrayList foundItems, + ExpenditureList itemList) throws MindMyMoneyException { + try { + for (Expenditure item : itemList.expenditureListArray) { + if (item.getAmount() == Float.parseFloat(searchTerm)) { + foundItems.add(item); + } + } + } catch (NumberFormatException e) { + throw new MindMyMoneyException("AMOUNT must be a number"); + } + return foundItems; + } + + /** + * Searches for matching items in Time field of itemList and returns a list of found items. + * + * @param searchTerm String to search for. + * @param foundItems List to store items found. + * @param itemList List to search in. + * @return foundItems, updated with new items found. + */ + public static ArrayList findMatchingTime(String searchTerm, ArrayList foundItems, + ExpenditureList itemList) { + for (Expenditure item : itemList.expenditureListArray) { + if (item.getTime().contains(searchTerm)) { + foundItems.add(item); + } + } + return foundItems; + } + + /** + * Sets the string to lower case and then capitalise the first character in string. + * + * @param str String to be capitalised. + * @return Capitalised string. + */ + public static String capitalise(String str) { + str = str.toLowerCase(); + return str.substring(INDEX_OF_FIRST_ITEM, INDEX_OF_SECOND_ITEM).toUpperCase() + + str.substring(INDEX_OF_SECOND_ITEM); + } + + /** + * Round off float to 2dp. + * + * @param number float to be rounded off. + * @return float rounded off to 2dp. + */ + public static float formatFloat(Float number) { + return Float.parseFloat(df.format(number)); + } +} diff --git a/src/main/java/seedu/mindmymoney/helper/SerializerFunctions.java b/src/main/java/seedu/mindmymoney/helper/SerializerFunctions.java new file mode 100644 index 0000000000..b023006ad7 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/helper/SerializerFunctions.java @@ -0,0 +1,77 @@ +package seedu.mindmymoney.helper; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.userfinancial.MindMyMoneySerializable; + +import java.util.ArrayList; +import java.util.Scanner; + +public class SerializerFunctions { + + public static final String SERIALIZATION_EXPENDITURE_START_MARKER = "# BEGIN EXPENDITURES"; + public static final String SERIALIZATION_CREDIT_CARD_START_MARKER = "# BEGIN CREDIT CARDS"; + public static final String SERIALIZATION_INCOME_START_MARKER = "# BEGIN INCOME SOURCES"; + public static final String SERIALIZATION_EXPENDITURE_END_MARKER = "# END EXPENDITURES"; + public static final String SERIALIZATION_CREDIT_CARD_END_MARKER = "# END CREDIT CARDS"; + public static final String SERIALIZATION_INCOME_END_MARKER = "# END INCOME SOURCES"; + + public interface DeserializerFunction { + T apply(String s) throws MindMyMoneyException; + } + + /** + * Adds an ArrayList of objects that implement MMMSerializable line-by-line into a StringBuilder. + * The list will have a start marker and an end marker placed before and after the list, respectively, + * to help with deserializing this list. + * @param startMarker A String marking the start of the list. + * @param endMarker A String marking the end of the list. + * @param list The list to add. + * @param stringBuilder A StringBuilder to add to. + * @param A MMMSerializable type to add. <\T> + */ + public static + void addListToStringBuilder(String startMarker, String endMarker, + ArrayList list, StringBuilder stringBuilder) { + stringBuilder.append(startMarker).append("\n"); + for (T serializable : list) { + stringBuilder.append(serializable.serialize()).append("\n"); + } + stringBuilder.append(endMarker).append("\n"); + } + + /** + * Reads a list of MMMSerializables from a Scanner. The list should start with startMarker, and end + * with endMarker. Each line in between will be passed to a deserializer function that converts + * the line into an MMMSerializable. + * @param startMarker A String marking the start of the list. + * @param endMarker A String marking the end of the list. + * @param scanner The Scanner to read from. + * @param deserializer A function that accepts a string and deserializes it. + * @param A MMMSerializable type to convert to. <\T> + * @return An ArrayList of list elements. + */ + public static + ArrayList convertInputToList(String startMarker, String endMarker, + Scanner scanner, + DeserializerFunction deserializer) throws MindMyMoneyException { + ArrayList list = new ArrayList(); + if (!scanner.hasNextLine()) { + return list; + } + String nextLine = scanner.nextLine(); + if (!nextLine.equals(startMarker)) { + throw new MindMyMoneyException("Expected " + startMarker + ", got " + nextLine); + } + while (true) { + if (!scanner.hasNextLine()) { + throw new MindMyMoneyException("Expected " + endMarker + " , got EOF"); + } + nextLine = scanner.nextLine(); + if (nextLine.equals(endMarker)) { + break; + } + list.add(deserializer.apply(nextLine)); + } + return list; + } +} diff --git a/src/main/java/seedu/mindmymoney/helper/TimeFunctions.java b/src/main/java/seedu/mindmymoney/helper/TimeFunctions.java new file mode 100644 index 0000000000..1dde99d57a --- /dev/null +++ b/src/main/java/seedu/mindmymoney/helper/TimeFunctions.java @@ -0,0 +1,87 @@ +package seedu.mindmymoney.helper; + +import seedu.mindmymoney.MindMyMoneyException; + +import java.time.LocalDate; + +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_THIRD_ITEM; +import static seedu.mindmymoney.constants.ValidationRegexTypes.VALIDATION_REGEX_D; +import static seedu.mindmymoney.constants.ValidationRegexTypes.VALIDATION_REGEX_M; +import static seedu.mindmymoney.constants.ValidationRegexTypes.VALIDATION_REGEX_Y; + +/** + * Container for functions needed to calculate and format time. + */ +public class TimeFunctions { + private static final int LEAP_YEAR_NUMBER = 4; + + /** + * Checks if date input format is valid. + * + * @param input The string of the date input. + * @return true if format is valid, false otherwise. + */ + public static boolean isValidInputCalculateCommand(String input) { + if (input.matches(VALIDATION_REGEX_D) + || input.matches(VALIDATION_REGEX_M) + || input.matches(VALIDATION_REGEX_Y)) { + return true; + } + return false; + + } + + /** + * Checks if date input format is valid. + * + * @param input The string of the date input. + * @return true if format is valid, false otherwise. + */ + public static boolean isValidInputAddCommand(String input) { + if (input.matches(VALIDATION_REGEX_D)) { + return true; + } + return false; + } + + /** + * Checks is parsed date is a valid date in the calendar. + * + * @param inputTime date that is parsed in. + * @throws MindMyMoneyException throws an exception when the date parsed is in not in the calendar. + */ + public static void checkValidDate(String inputTime) throws MindMyMoneyException { + if (!isValidInputAddCommand(inputTime)) { + throw new MindMyMoneyException("Date has to be valid and in this format \"dd/mm/yyyy\""); + } + String[] date = inputTime.split("/"); + String day = date[INDEX_OF_FIRST_ITEM]; + int dayInInt = Integer.parseInt(day); + String month = date[INDEX_OF_SECOND_ITEM]; + String year = date[INDEX_OF_THIRD_ITEM]; + int yearInInt = Integer.parseInt(year); + if (!(yearInInt % LEAP_YEAR_NUMBER == 0) && month.equals("02") && (dayInInt > 28)) { + throw new MindMyMoneyException(day + "/" + month + " is not a valid dd/mm in a non leap year!"); + } else if ((yearInInt % LEAP_YEAR_NUMBER == 0) && month.equals("02") && (dayInInt > 29)) { + throw new MindMyMoneyException(day + "/" + month + " is not a valid dd/mm in a leap year!"); + } else if ((month.equals("04") || month.equals("06") || month.equals("09") || month.equals("11")) + && dayInInt > 30) { + throw new MindMyMoneyException(day + "/" + month + " is not a valid dd/mm in this month!"); + } + } + + /** + * Checks if parsed date is after the current date. + * + * @param date date that is parsed in. + * @throws MindMyMoneyException throws an exception when the date parsed is after current date. + */ + public static void checkAfterCurrentDate(LocalDate date) throws MindMyMoneyException { + LocalDate currentDate = LocalDate.now(); + if (date.isAfter(currentDate)) { + throw new MindMyMoneyException("Please enter a valid date that is before today or today's date itself."); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/helper/ValidatorFunctions.java b/src/main/java/seedu/mindmymoney/helper/ValidatorFunctions.java new file mode 100644 index 0000000000..c39e73618f --- /dev/null +++ b/src/main/java/seedu/mindmymoney/helper/ValidatorFunctions.java @@ -0,0 +1,135 @@ +package seedu.mindmymoney.helper; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.ValidationException; + +import java.util.HashSet; + +/** Class for helper functions in validating the save file. */ +public class ValidatorFunctions { + + /** + * Checks if value is above lowerBound, or if inclusive is true, if it is equal. Throws + * a ValidationException if this condition is not met. + * @param value The value to check. + * @param lowerBound The lower bound to validate against. + * @param inclusive Whether or not to accept value if it is equal to lowerBound. + * @param label A label to use in the error message. + * @throws ValidationException if the value does not satisfy the lower bound. + */ + public static void validateLowerBound(int value, int lowerBound, boolean inclusive, String label) + throws ValidationException { + if (value > lowerBound) { + return; + } + if (value == lowerBound && inclusive) { + return; + } + String comparatorInMessage = (inclusive ? ">=" : ">"); + throw new ValidationException("Values for " + label + " should be " + comparatorInMessage + + Integer.toString(lowerBound)); + } + + /** + * Checks if value is between lower and upper, inclusive. Throws a ValidationException + * if this condition is not met. + * @param value The value to check. + * @param lower The lower bound. + * @param upper The upper bound. + * @param label A label to use in the error message. + * @throws ValidationException if the value is not in range. + */ + public static void validateInRange(double value, double lower, double upper, String label) + throws ValidationException { + if (lower <= value && value <= upper) { + return; + } + throw new ValidationException("Values for " + label + " should be between " + + Double.toString(lower) + " and " + Double.toString(upper)); + } + + /** + * Checks if category is a valid category for Incomes. Throws a ValidationException + * if this condition is not met. + * @param category The category to check. + * @throws ValidationException if the category is invalid. + */ + public static void validateIncomeCategory(String category) throws ValidationException { + if (AddCommandInputTests.isIncomeCategoryInList(category)) { + return; + } + throw new ValidationException(category + " is an invalid income category"); + } + + /** + * Checks if category is a valid category for Expenditures. Throws a ValidationException + * if this condition is not met. + * @param category The category to check. + * @throws ValidationException if the category is invalid. + */ + public static void validateExpenditureCategory(String category) throws ValidationException { + if (AddCommandInputTests.isExpenditureCategoryInList(category)) { + return; + } + throw new ValidationException(category + " is an invalid expenditure category"); + } + + /** + * Check if time is a valid date for Expenditures. Throws a ValidationException if + * this condition is not met. + * @param time The time to check. + * @throws ValidationException if the date is invalid. + */ + public static void validateDate(String time) throws ValidationException { + try { + TimeFunctions.checkValidDate(time); + } catch (MindMyMoneyException e) { + throw new ValidationException(time + " is an invalid date:" + e.getMessage()); + } + } + + /** + * Checks if all payment methods in an ExpenditureList is either Cash, or can be found + * in a CreditCardList. Throws a ValidationException if this condition is not met. + * @param expenditures The ExpenditureList to validate. + * @param creditCards The CreditCardList to validate against. + * @throws ValidationException if a payment method is invalid. + */ + public static void validatePaymentMethods(ExpenditureList expenditures, + CreditCardList creditCards) throws ValidationException { + HashSet creditCardNames = new HashSet<>(); + for (CreditCard creditCard : creditCards.creditCardListArray) { + creditCardNames.add(creditCard.getNameOfCard()); + } + for (Expenditure expenditure : expenditures.expenditureListArray) { + if (expenditure.getPaymentMethod().equals("Cash")) { + continue; + } + if (!creditCardNames.contains(expenditure.getPaymentMethod())) { + throw new ValidationException(expenditure.getPaymentMethod() + + " does not appear as a credit card"); + } + } + } + + /** + * Checks if no two credit cards in a CreditCardList have the same name. Throws a ValidationException if this + * condition is not met. + * @param creditCards The CreditCardList to validate + * @throws ValidationException if a name repeats. + */ + public static void validateCreditCardNames(CreditCardList creditCards) throws ValidationException { + HashSet creditCardNames = new HashSet<>(); + for (CreditCard creditCard : creditCards.creditCardListArray) { + String name = creditCard.getNameOfCard(); + if (creditCardNames.contains(name)) { + throw new ValidationException(name + " appears twice in credit card list"); + } + creditCardNames.add(name); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/userfinancial/CreditCard.java b/src/main/java/seedu/mindmymoney/userfinancial/CreditCard.java new file mode 100644 index 0000000000..5426a3e270 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/userfinancial/CreditCard.java @@ -0,0 +1,132 @@ +package seedu.mindmymoney.userfinancial; + + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.PropertyList; +import seedu.mindmymoney.helper.ValidatorFunctions; + +import static seedu.mindmymoney.constants.CalculationConversion.FLOAT_TO_PERCENTAGE; +import static seedu.mindmymoney.helper.GeneralFunctions.formatFloat; + + +/** + * Represents the credit card entry. + */ +public class CreditCard implements MindMyMoneySerializable { + private float monthlyCardLimit; + private double cashback; + private String nameOfCard; + private float totalExpenditure = 0; + + public CreditCard(String nameOfCard, double cashback, float monthlyCardLimit) { + setNameOfCard(nameOfCard); + setCashback(cashback); + setMonthlyCardLimit(monthlyCardLimit); + } + + public void setNameOfCard(String nameOfCard) { + this.nameOfCard = nameOfCard; + } + + public String getNameOfCard() { + return nameOfCard; + } + + public void setCashback(double cashback) { + this.cashback = cashback; + } + + public double getCashback() { + return cashback; + } + + public void setMonthlyCardLimit(float monthlyCardLimit) { + this.monthlyCardLimit = monthlyCardLimit; + } + + public float getMonthlyCardLimit() { + return monthlyCardLimit; + } + + public float getTotalExpenditure() { + return totalExpenditure; + } + + public float getBalanceLeft() { + return monthlyCardLimit - totalExpenditure; + } + + public void addExpenditure(float amount) { + this.totalExpenditure += amount; + } + + public void deductExpenditure(float amount) { + this.totalExpenditure -= amount; + } + + public float getTotalCashback() { + return formatFloat((float)(totalExpenditure * (cashback * FLOAT_TO_PERCENTAGE))); + } + + @Override + public String toString() { + return "Name: " + getNameOfCard() + " [Cashback: " + String.format("%.2f", getCashback()) + + "%] [Cashback gained: $" + String.format("%.2f", getTotalCashback()) + + "] [Card limit: $" + String.format("%.2f", getMonthlyCardLimit()) + + "] [Balance left: $" + String.format("%.2f", getBalanceLeft()) + "]\n"; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof CreditCard)) { + return false; + } + CreditCard creditCard = (CreditCard) object; + return nameOfCard.equals(creditCard.nameOfCard) && (cashback == creditCard.cashback) + && (monthlyCardLimit == creditCard.monthlyCardLimit); + } + + /** + * Returns a String representation of this credit card, in a machine-readable format. + * @return The serialized CreditCard. + */ + public String serialize() { + PropertyList plist = new PropertyList(); + plist.addProperty("monthlyCardLimit", Float.toString(monthlyCardLimit)); + plist.addProperty("cashback", Double.toString(cashback)); + plist.addProperty("nameOfCard", nameOfCard); + plist.addProperty("totalExpenditure", Float.toString(totalExpenditure)); + return plist.serialize(); + } + + /** + * Converts the output of CreditCard#serialize back into a CreditCard. + * @param serialized The serialized CreditCard + * @return A CreditCard. + * @throws MindMyMoneyException if the format is invalid. + */ + public static CreditCard deserialize(String serialized) throws MindMyMoneyException { + PropertyList plist = PropertyList.deserialize(serialized); + try { + double cashback = Double.parseDouble(plist.getValue("cashback")); + double monthlyCardLimit = Double.parseDouble(plist.getValue("monthlyCardLimit")); + double totalExpenditure = Double.parseDouble(plist.getValue("totalExpenditure")); + ValidatorFunctions.validateInRange(cashback, 0, 100, "cashback"); + ValidatorFunctions.validateInRange(monthlyCardLimit, 0, 40000, "monthly limit"); + ValidatorFunctions.validateInRange(totalExpenditure, 0, monthlyCardLimit, "total expenditures"); + CreditCard cc = new CreditCard(plist.getValue("nameOfCard"), + cashback, + (float) monthlyCardLimit); + cc.totalExpenditure = (float) totalExpenditure; + return cc; + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Invalid number during deserialization of " + serialized); + } catch (ValidationException e) { + throw e; + } catch (MindMyMoneyException e) { + String missingProperty = e.getMessage(); + throw new MindMyMoneyException("Line [" + serialized + "] does not contain required value " + + missingProperty); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/userfinancial/Expenditure.java b/src/main/java/seedu/mindmymoney/userfinancial/Expenditure.java new file mode 100644 index 0000000000..a7f4b9c5de --- /dev/null +++ b/src/main/java/seedu/mindmymoney/userfinancial/Expenditure.java @@ -0,0 +1,136 @@ +package seedu.mindmymoney.userfinancial; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.PropertyList; +import seedu.mindmymoney.helper.ValidatorFunctions; + + +import java.util.Objects; + +/** + * Represents the expenditure entry. + */ +public class Expenditure implements MindMyMoneySerializable { + private String description; + private float amount; + private String category; + private String paymentMethod; + private String time; + + public Expenditure(String paymentMethod, String category, String description, float amount, String time) { + setDescription(description); + setAmount(amount); + setCategory(category); + setPaymentMethod(paymentMethod); + setTime(time); + } + + public void setAmount(float amount) { + this.amount = amount; + } + + public float getAmount() { + return amount; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getCategory() { + return category; + } + + public void setPaymentMethod(String expenditure) { + this.paymentMethod = expenditure; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setTime(String time) { + this.time = time; + } + + public String getTime() { + return time; + } + + @Override + public String toString() { + return "$" + getAmount() + " on " + getDescription() + ". Paid using " + + getPaymentMethod() + " [" + getCategory() + "]" + " [" + getTime() + "]"; + + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Expenditure)) { + return false; + } + Expenditure expenditure = (Expenditure) object; + return description.equals(expenditure.description) && (amount == expenditure.amount) + && category.equals(expenditure.category) && paymentMethod.equals(expenditure.paymentMethod) + && time.equals(expenditure.time); + } + + @Override + public int hashCode() { + return Objects.hash(description, amount, category, paymentMethod, time); + } + + + /** + * Returns a String representation of this expenditure, in a machine-readable format. + * @return The serialized Expenditure. + */ + public String serialize() { + PropertyList plist = new PropertyList(); + plist.addProperty("description", description); + plist.addProperty("category", category); + plist.addProperty("paymentMethod", paymentMethod); + plist.addProperty("time", time); + plist.addProperty("amount", Float.toString(amount)); + return plist.serialize(); + } + + /** + * Converts the output of Expenditure#serialize back into an Expenditure. + * @param serialized The serialized Expenditure + * @return An Expenditure. + * @throws MindMyMoneyException if the format is invalid. + */ + public static Expenditure deserialize(String serialized) throws MindMyMoneyException { + PropertyList plist = PropertyList.deserialize(serialized); + try { + String category = plist.getValue("category"); + ValidatorFunctions.validateExpenditureCategory(category); + float amount = Float.parseFloat(plist.getValue("amount")); + ValidatorFunctions.validateInRange(amount, 0, Float.POSITIVE_INFINITY, "amount"); + String time = plist.getValue("time"); + ValidatorFunctions.validateDate(time); + return new Expenditure(plist.getValue("paymentMethod"), + category, + plist.getValue("description"), + amount, + time); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Invalid number for amount during deserialization of " + serialized); + } catch (ValidationException e) { + throw e; + } catch (MindMyMoneyException e) { + String missingProperty = e.getMessage(); + throw new MindMyMoneyException("Line [" + serialized + "] does not contain required value " + + missingProperty); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/userfinancial/Income.java b/src/main/java/seedu/mindmymoney/userfinancial/Income.java new file mode 100644 index 0000000000..a8d1315112 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/userfinancial/Income.java @@ -0,0 +1,84 @@ +package seedu.mindmymoney.userfinancial; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.PropertyList; +import seedu.mindmymoney.helper.ValidatorFunctions; + +/** + * Represents the income entry. + */ +public class Income implements MindMyMoneySerializable { + private int amount; + private String category; + + public Income(int amount, String category) { + this.amount = amount; + this.category = category; + } + + public int getAmount() { + return amount; + } + + public String getCategory() { + return category; + } + + /** + * Returns the income entry as a string format. + * + * @return String format. + */ + @Override + public String toString() { + String incomeInfo = "Amount: $" + amount + "\n" + + " Category: " + category + "\n"; + return incomeInfo; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Income)) { + return false; + } + Income income = (Income) object; + return (amount == income.amount) && (category.equals(income.category)); + } + + /** + * Returns a String representation of this income source, in a machine-readable format. + * @return The serialized Income. + */ + public String serialize() { + PropertyList plist = new PropertyList(); + plist.addProperty("category", category); + plist.addProperty("amount", Integer.toString(amount)); + return plist.serialize(); + } + + /** + * Converts the output of Income#serialize back into an Income. + * @param serialized The serialized Income. + * @return An Income. + * @throws MindMyMoneyException if the format is invalid. + */ + public static Income deserialize(String serialized) throws MindMyMoneyException { + PropertyList plist = PropertyList.deserialize(serialized); + try { + int amount = Integer.parseInt(plist.getValue("amount")); + String category = plist.getValue("category"); + ValidatorFunctions.validateIncomeCategory(category); + ValidatorFunctions.validateLowerBound(amount, 0, true, "amount"); + return new Income(Integer.parseInt(plist.getValue("amount")), + plist.getValue("category")); + } catch (NumberFormatException e) { + throw new MindMyMoneyException("Invalid number for amount during deserialization of " + serialized); + } catch (ValidationException e) { + throw e; + } catch (MindMyMoneyException e) { // catches errors that are NOT ValidationExceptions + String missingProperty = e.getMessage(); + throw new MindMyMoneyException("Line [" + serialized + "] does not contain required value " + + missingProperty); + } + } +} diff --git a/src/main/java/seedu/mindmymoney/userfinancial/MindMyMoneySerializable.java b/src/main/java/seedu/mindmymoney/userfinancial/MindMyMoneySerializable.java new file mode 100644 index 0000000000..02eeb07335 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/userfinancial/MindMyMoneySerializable.java @@ -0,0 +1,11 @@ +package seedu.mindmymoney.userfinancial; + +/** + * Interface for serializable objects. + * Note that this is different from java.io.Serializable, + * as classes implementing this interface should ensure that their + * serializations are human-readable. + */ +public interface MindMyMoneySerializable { + String serialize(); +} diff --git a/src/main/java/seedu/mindmymoney/userfinancial/User.java b/src/main/java/seedu/mindmymoney/userfinancial/User.java new file mode 100644 index 0000000000..8b29b50fa3 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/userfinancial/User.java @@ -0,0 +1,89 @@ +package seedu.mindmymoney.userfinancial; + +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.helper.ValidatorFunctions; + +import java.util.Scanner; + +/** + * Represents the user. + */ +public class User { + private ExpenditureList expenditureListArray; + private CreditCardList creditCardListArray; + private IncomeList incomeListArray; + + public User() { + setExpenditureListArray(new ExpenditureList()); + setCreditCardListArray(new CreditCardList()); + setIncomeListArray(new IncomeList()); + } + + public User(ExpenditureList expenditureListArray, CreditCardList creditCardListArray, + IncomeList incomeListArray) { + setExpenditureListArray(expenditureListArray); + setCreditCardListArray(creditCardListArray); + setIncomeListArray(incomeListArray); + } + + public void setExpenditureListArray(ExpenditureList expenditureListArray) { + this.expenditureListArray = expenditureListArray; + } + + public void setCreditCardListArray(CreditCardList creditCardListArray) { + this.creditCardListArray = creditCardListArray; + } + + public void setIncomeListArray(IncomeList incomeListArray) { + this.incomeListArray = incomeListArray; + } + + public ExpenditureList getExpenditureListArray() { + return expenditureListArray; + } + + public CreditCardList getCreditCardListArray() { + return creditCardListArray; + } + + public IncomeList getIncomeListArray() { + return incomeListArray; + } + + /** + * Returns a String representation of this user in a machine-readable format. + * @return A serialized User. + */ + public String serialize() { + StringBuilder sb = new StringBuilder(); + sb.append(expenditureListArray.serialize()); + sb.append(creditCardListArray.serialize()); + sb.append(incomeListArray.serialize()); + return sb.toString(); + } + + + /** + * Converts the output of User#serialized back into a User. This method reads from + * a Scanner + * @param scanner A Scanner from which to read a serialized User. + * @return The User. + * @throws MindMyMoneyException if the format is incorrect. + */ + public static User deserializeFrom(Scanner scanner) throws MindMyMoneyException { + User savedUser = new User(); + + savedUser.setExpenditureListArray(ExpenditureList.deserializeFrom(scanner)); + savedUser.setCreditCardListArray(CreditCardList.deserializeFrom(scanner)); + savedUser.setIncomeListArray(IncomeList.deserializeFrom(scanner)); + + ValidatorFunctions.validateCreditCardNames(savedUser.creditCardListArray); + ValidatorFunctions.validatePaymentMethods(savedUser.expenditureListArray, savedUser.creditCardListArray); + + return savedUser; + } + +} diff --git a/src/main/java/seedu/mindmymoney/userfinancial/ValidationException.java b/src/main/java/seedu/mindmymoney/userfinancial/ValidationException.java new file mode 100644 index 0000000000..57474027c2 --- /dev/null +++ b/src/main/java/seedu/mindmymoney/userfinancial/ValidationException.java @@ -0,0 +1,10 @@ +package seedu.mindmymoney.userfinancial; + +import seedu.mindmymoney.MindMyMoneyException; + +/** Class for representing validation errors. */ +public class ValidationException extends MindMyMoneyException { + public ValidationException(String s) { + super(s); + } +} 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/seedu/mindmymoney/MindMyMoneyTest.java b/src/test/java/seedu/mindmymoney/MindMyMoneyTest.java new file mode 100644 index 0000000000..782bfc43aa --- /dev/null +++ b/src/test/java/seedu/mindmymoney/MindMyMoneyTest.java @@ -0,0 +1,21 @@ +package seedu.mindmymoney; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; + +class MindMyMoneyTest { + + /** + * Asserts if MMM is able to run and await for new line. + * If a NoSuchElementException occurs, MM is trying to read a newline but unable to, hence ran successfully. + */ + @Test + public void mindMyMoney_runAndInputNewLine_expectedException() { + assertThrows(NoSuchElementException.class, + () -> new MindMyMoney().run()); + + } +} diff --git a/src/test/java/seedu/mindmymoney/ParserTest.java b/src/test/java/seedu/mindmymoney/ParserTest.java new file mode 100644 index 0000000000..97298eb5c8 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/ParserTest.java @@ -0,0 +1,135 @@ +package seedu.mindmymoney; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.command.ByeCommand; +import seedu.mindmymoney.command.CalculateInputCommand; +import seedu.mindmymoney.command.DeleteCommand; +import seedu.mindmymoney.command.ListCommand; +import seedu.mindmymoney.command.AddCommand; +import seedu.mindmymoney.command.UpdateCommand; +import seedu.mindmymoney.command.HelpCommand; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.User; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ParserTest { + private final ByteArrayOutputStream capturedOut = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + public void setUp() { + System.setOut(new PrintStream(capturedOut)); + } + + public static final int TEST_PRICE = 1; //arbitrary number for testing + public static final int TEST_INDEX = 0; //arbitrary number for testing + + /** + * Checks Parser.parseCommand() that it returns a Command object for each test input. + */ + @Test + void parseCommand_normalInput_expectCorrectCommandObject() { + String testInput = "help"; + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeTestList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeTestList); + Parser testParser = new Parser(); + + assertTrue(testParser.parseCommand(testInput, user) instanceof HelpCommand); + assert testParser.parseCommand(testInput, user) + instanceof HelpCommand : "testParser should return an " + "instance of HelpCommand"; + + testInput = "add description " + TEST_PRICE; + assertTrue(testParser.parseCommand(testInput, user) instanceof AddCommand); + + testInput = "update " + TEST_INDEX + " description " + TEST_PRICE; + assertTrue(testParser.parseCommand(testInput, user) instanceof UpdateCommand); + + testInput = "list /e"; + assertTrue(testParser.parseCommand(testInput, user) instanceof ListCommand); + + testInput = "delete " + TEST_INDEX; + assertTrue(testParser.parseCommand(testInput, user) instanceof DeleteCommand); + + testInput = "bye"; + assertTrue(testParser.parseCommand(testInput, user) instanceof ByeCommand); + + testInput = "calculate /epm 01/02/2022"; + assertTrue(testParser.parseCommand(testInput, user) instanceof CalculateInputCommand); + } + + /** + * Invalid input by user should return a HelpCommand object + * and print an invalid command message. + */ + @Test + void parseCommand_invalidInput_expectHelpCommand() { + Parser testParser = new Parser(); + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeTestList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeTestList); + + assertTrue(testParser.parseCommand("", user) instanceof HelpCommand); + } + + /** + * "Help /cc" by user should return the credit card help page + * and print an invalid command message. + */ + @Test + void parseCommand_helpWithParameters_expectHelpCommand() throws MindMyMoneyException { + Parser testParser = new Parser(); + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeTestList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeTestList); + setUp(); + String expectedOutput = + "---------------------------------------Credit Card Help Page--------------------------" + + "-------------\n" + + "1. Listing all Credit Cards: list /cc\n" + + "2. Adding a Credit Card: add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CREDIT_LIMIT]\n" + + "3. Updating a Credit Card: update /cc [INDEX] /n [NEW_NAME] /cb [NEW_CASHBACK] " + + "/cl [NEW_CREDIT_LIMIT]\n" + + "4. Removing a credit card: delete /cc [INDEX]\n" + + "5. Exiting the program: bye\n" + + "-----------------------------------------------------------------------------------------------" + + "----"; + (testParser.parseCommand("help /cc", user)).executeCommand(); + tearDown(); + assertEquals(expectedOutput, capturedOut.toString().trim()); + } + + /** + * Add input without parameters should return a HelpCommand object + * and print an invalid command message. + */ + @Test + void parseCommand_addWithoutParameters_expectException() throws MindMyMoneyException { + Parser testParser = new Parser(); + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeTestList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeTestList); + setUp(); + String expectedOutput = "Invalid command! \n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands"; + (testParser.parseCommand("add", user)).executeCommand(); + tearDown(); + assertEquals(expectedOutput, capturedOut.toString().trim()); + } + + public void tearDown() { + System.setOut(stdout); + } +} diff --git a/src/test/java/seedu/mindmymoney/StorageTest.java b/src/test/java/seedu/mindmymoney/StorageTest.java new file mode 100644 index 0000000000..52969a9258 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/StorageTest.java @@ -0,0 +1,62 @@ +package seedu.mindmymoney; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.User; + +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class StorageTest { + @TempDir + File storageDir; + + @Test + void initialStartup_nonexistentFile_emptyList() { + File storageFile = new File(storageDir, "list.txt"); + try { + Storage storage = new Storage(storageFile); + } catch (MindMyMoneyException e) { + fail(); + } + } + + @Test + void normalUse_savedFile_listRetrieved() { + File storageFile = new File(storageDir, "list.txt"); + try { + storageFile.createNewFile(); + } catch (IOException e) { + Assertions.fail("Creating storage file for test threw IOException"); + } + User savedUser = new User(); + savedUser.setCreditCardListArray(new CreditCardList()); + savedUser.setIncomeListArray(new IncomeList()); + + ExpenditureList expenditureList = new ExpenditureList(); + expenditureList.add(new Expenditure("Cash", "Food", + "test", 1, "05/03/2022")); + expenditureList.add(new Expenditure("Cash", "Food", + "Make tests", 999, "05/03/2022")); + + savedUser.setExpenditureListArray(expenditureList); + + try { + Storage storage = new Storage(storageFile); + storage.save(savedUser); + User loadedUser = storage.load(); + assertEquals(expenditureList.expenditureListArray, + loadedUser.getExpenditureListArray().expenditureListArray); + } catch (MindMyMoneyException e) { + fail(); + } + } +} diff --git a/src/test/java/seedu/mindmymoney/UiTest.java b/src/test/java/seedu/mindmymoney/UiTest.java new file mode 100644 index 0000000000..2271394a00 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/UiTest.java @@ -0,0 +1,5 @@ +package seedu.mindmymoney; + +class UiTest { + +} \ No newline at end of file diff --git a/src/test/java/seedu/mindmymoney/command/AddCommandTest.java b/src/test/java/seedu/mindmymoney/command/AddCommandTest.java new file mode 100644 index 0000000000..e044796ffd --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/AddCommandTest.java @@ -0,0 +1,622 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.Income; +import seedu.mindmymoney.userfinancial.User; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.mindmymoney.constants.Indexes.LIST_INDEX_CORRECTION; + +class AddCommandTest { + + /** + * Asserts if user is able to add an input with cash. + */ + @Test + void addCommand_oneExpenditureInput_expectExpenditureListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + ArrayList testList = new ArrayList<>(); + testList.add(new Expenditure("Cash", "Personal", "Nike Shoes", + 300, "30/03/2022")); + String expectedOutput = getExpenditureOutput(testList); + String actualOutput = getExpenditureOutput(expenditureTestList.expenditureListArray); + assertEquals(expectedOutput, actualOutput); + testList.clear(); + } + + /** + * Tests for insertion of credit card with valid parameters. + */ + @Test + void addCommand_validAddCreditCardInput_expectCreditCardListUpdate() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/cc /n DBS /cb 1.5 /cl 500"; + new AddCommand(inputString, user).executeCommand(); + ArrayList testList = new ArrayList<>(); + testList.add(new CreditCard("DBS", 1.5, 500)); + String expectedOutput = testList.get(0).toString(); + String actualOutput = user.getCreditCardListArray().get(0).toString(); + assertEquals(expectedOutput, actualOutput); + testList.clear(); + } + + /** + * Asserts if user is able to add an input that is not case-sensitive. + */ + @Test + void addCommand_caseInsensitiveExpenditureInput_expectExpenditureListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cASh /c PerSONal /d Nike Shoes /a 300 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + ArrayList testList = new ArrayList<>(); + testList.add(new Expenditure("Cash", "Personal", "Nike Shoes", + 300, "30/03/2022")); + String expectedOutput = getExpenditureOutput(testList); + String actualOutput = getExpenditureOutput(expenditureTestList.expenditureListArray); + assertEquals(expectedOutput, actualOutput); + testList.clear(); + } + + /** + * Asserts if user is able to add an amount that has more decimal place than float. + */ + @Test + void addCommand_wrongDecimalPlaceExpenditureInput_expectExpenditureListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm Cash /c Personal /d Nike Shoes /a 300.1299786222834 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + ArrayList testList = new ArrayList<>(); + testList.add(new Expenditure("Cash", "Personal", "Nike Shoes", + (float) 300.13, "30/03/2022")); + String expectedOutput = getExpenditureOutput(testList); + String actualOutput = getExpenditureOutput(expenditureTestList.expenditureListArray); + assertEquals(expectedOutput, actualOutput); + testList.clear(); + } + + /** + * Asserts if user is able to add an expenditure input with credit card. + */ + @Test + void addCommand_expenditureWithCreditCardInput_expectExpenditureListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + creditCardTestList.add(new CreditCard("posb", 0.05, 500)); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm posb /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + ArrayList testList = new ArrayList<>(); + testList.add(new Expenditure("posb", "Personal", "Nike Shoes", + 300, "30/03/2022")); + String expectedOutput = getExpenditureOutput(testList); + String actualOutput = getExpenditureOutput(expenditureTestList.expenditureListArray); + assertEquals(expectedOutput, actualOutput); + testList.clear(); + } + + /** + * Asserts if user is able to add an Income entry. + * + * @throws MindMyMoneyException when an invalid command is received. + */ + @Test + void addCommand_incomeInput_expectIncomeListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/i /a 3000 /c Salary"; + new AddCommand(inputString, user).executeCommand(); + + ArrayList testList = new ArrayList<>(); + testList.add(new Income(3000, "Salary")); + + String expectedOutput = getIncomeOutput(testList); + String actualOutput = getIncomeOutput(incomeList.incomeListArray); + assertEquals(expectedOutput, actualOutput); + + testList.clear(); + } + + /** + * Asserts if user is able to add an Income entry that is not case-sensitive. + */ + @Test + void addCommand_caseInsensitiveIncomeInput_expectIncomeListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/i /a 3000 /c SaLaRy"; + new AddCommand(inputString, user).executeCommand(); + + ArrayList testList = new ArrayList<>(); + testList.add(new Income(3000, "Salary")); + + String expectedOutput = getIncomeOutput(testList); + String actualOutput = getIncomeOutput(incomeList.incomeListArray); + assertEquals(expectedOutput, actualOutput); + + testList.clear(); + } + + /** + * Asserts if user is able to add an empty input. + */ + @Test + void addCommand_missingIncomeInput_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = ""; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add a non-numerical amount. + */ + @Test + void addCommand_nonNumberIncomeAmount_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a abcd /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an incorrect flag. + */ + @Test + void addCommand_incorrectExpenditureFlags_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /z Personal /d Nike Shoes /a 500 /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an incorrect order of flag. + */ + @Test + void addCommand_incorrectOrderOfExpenditureFlags_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /d Nike Shoes /a 500 /t 30/03/2022 /c Personal"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an incorrect Expenditure Method. + */ + @Test + void addCommand_incorrectExpenditurePaymentMethod_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm casssh /c Personal /d Nike Shoes /a 500 /t 30/03/2022 "; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an incorrect Category. + */ + @Test + void addCommand_incorrectExpenditureCategory_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an incorrect Date. + */ + @Test + void addCommand_incorrectExpenditureDate_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String firstInputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 30/4/2022"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(firstInputString, user).executeCommand()); + String secondInputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 04/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(secondInputString, user).executeCommand()); + String thirdInputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(thirdInputString, user).executeCommand()); + + String fourthInputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 38/14/2022"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(fourthInputString, user).executeCommand()); + + String fifthInputString = "/pm cash /c Food /d Porridge /a 4.50 /t 31/11/2021"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(fifthInputString, user).executeCommand()); + + String sixthInputString = "/pm cash /c Food /d Porridge /a 4.50 /t 29/02/2021"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(sixthInputString, user).executeCommand()); + + String seventhInputString = "/pm cash /c Food /d Porridge /a 4.50 /t 30/02/2020"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(seventhInputString, user).executeCommand()); + + String eighthInputString = "/pm cash /c Food /d Porridge /a 4.50 /t 31/04/2020"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(eighthInputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an empty expenditure method. + */ + @Test + void addCommand_nullExpenditurePaymentMethod_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm /c Person /d Nike Shoes /a 500 /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an empty category. + */ + @Test + void addCommand_nullExpenditureCategory_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm Cash /c /d Nike Shoes /a 500 /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an empty description. + */ + @Test + void addCommand_nullExpenditureDescription_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm Cash /c Food /d /a 500 /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an empty amount. + */ + @Test + void addCommand_nullExpenditureAmount_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm Cash /c Food /d Shoes /a /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add empty time. + */ + @Test + void addCommand_nullExpenditureDate_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm Cash /c Food /d Shoes /a 500 /t"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add a field with no spaces between flags. + */ + @Test + void addCommand_lackSpacingBetweenExpenditureFlags_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm/c Person /d Nike Shoes /a 500 /t 30/03/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an income entry with a non-numerical amount. + */ + @Test + void addCommand_notNumberIncomeAmount_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/i /a three-thousand /c Salary"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to add an income entry with an invalid income category. + */ + @Test + void addCommand_invalidIncomeCategory_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/i /a 3000 /c notAnIncomeCategory"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Tests for updating of credit card total expenditure when expenditure is used with credit card. + */ + @Test + void addCommand_validExpenditureInput_expectCreditCardTotalExpenditureUpdate() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + creditCardTestList.add(new CreditCard("dbs",0.05,500)); + assertEquals(creditCardTestList.get(0).getTotalExpenditure(), 0.0); + + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm dbs /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + assertEquals(creditCardTestList.get(0).getTotalExpenditure(), 300.0); + } + + /** + * Tests for exception thrown when an invalid Credit Card name is given. + */ + @Test + void addCommand_invalidAddCreditCardNameInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + new AddCommand("/cc /n DBS /cb 1.5 /cl 500", user).executeCommand(); + + String inputString = "/cc /n cash /cb 1.5 /cl 500"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(inputString, user).executeCommand()); + + String secondInputString = "/cc /n CASH /cb 1.5 /cl 500"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(secondInputString, user).executeCommand()); + + String thirdInputString = "/cc /n dbs /cb 1.5 /cl 500"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(thirdInputString, user).executeCommand()); + } + + /** + * Tests for exception thrown when an invalid Credit Card cashback is given. + */ + @Test + void addCommand_invalidAddCreditCardCashbackInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/cc /n cash /cb 101 /cl 500"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(inputString, user).executeCommand()); + + String secondInputString = "/cc /n cash /cb -1 /cl 500"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(secondInputString, user).executeCommand()); + } + + /** + * Tests for exception thrown when an invalid Credit Card limit is given. + */ + @Test + void addCommand_invalidAddCreditCardLimitInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/cc /n cash /cb 1.5 /cl 0"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(inputString, user).executeCommand()); + + String inputString2 = "/cc /n cash /cb 1.5 /cl -1"; + assertThrows(MindMyMoneyException.class, () -> new AddCommand(inputString2, user).executeCommand()); + } + + /** + * Asserts if user is able to add an improper flag. + */ + @Test + void addCommand_notProperFlag_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/asdf"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + } + + /** + * Asserts if user is able to an invalid time. + */ + @Test + void addCommand_notValidTime_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 30/032022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + + } + + /** + * Asserts if user is able to add a day larger than 29 feb. + */ + @Test + void addCommand_wrongDateForLeapYear_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 30/02/2020"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + + } + + /** + * Asserts if user is able to add a day larger than 30 for months with only 30 days. + */ + @Test + void addCommand_wrongDateForMonthWithThirtyDays_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 31/09/2018"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + + } + + /** + * Asserts if user is able to add a larger than 28 feb on a non leap year. + */ + @Test + void addCommand_wrongDateForNonLeapYear_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 29/02/2018"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(inputString, user).executeCommand()); + + } + + /** + * Test if program is able to exit. + */ + @Test + void addCommand_isExit_expectFalse() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Person /d Nike Shoes /a 500 /t 29/02/2018"; + + assertEquals(false, new AddCommand(inputString, user).isExit()); + + } + + /** + * Gets the last expenditure entry in the expenditure list and formats it to a string. + * + * @param list is the expenditure list. + * @return expenditure entry as a string if list is not empty, else it returns an empty string. + */ + public String getExpenditureOutput(ArrayList list) { + if (!list.isEmpty()) { + return list.get(list.size() + LIST_INDEX_CORRECTION).getPaymentMethod() + + list.get(list.size() + LIST_INDEX_CORRECTION).getCategory() + + list.get(list.size() + LIST_INDEX_CORRECTION).getDescription() + + list.get(list.size() + LIST_INDEX_CORRECTION).getAmount() + + list.get(list.size() + LIST_INDEX_CORRECTION).getTime(); + } + return ""; + } + + /** + * Gets the last income entry in the income list and formats it to a string. + * + * @param list is the income list. + * @return income entry as a string if list is not empty, else it returns an empty string. + */ + public String getIncomeOutput(ArrayList list) { + if (!list.isEmpty()) { + return list.get(list.size() + LIST_INDEX_CORRECTION).getAmount() + + list.get(list.size() + LIST_INDEX_CORRECTION).getCategory(); + } + return ""; + } +} diff --git a/src/test/java/seedu/mindmymoney/command/ByeCommandTest.java b/src/test/java/seedu/mindmymoney/command/ByeCommandTest.java new file mode 100644 index 0000000000..8d3d3fd288 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/ByeCommandTest.java @@ -0,0 +1,30 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ByeCommandTest { + private final ByteArrayOutputStream capturedOut = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + public void setUp() { + System.setOut(new PrintStream(capturedOut)); + } + + /** + * Asserts if command is able to exit. + */ + @Test + void byeCommand_isExit_expectByeMessage() { + assertEquals(true, new ByeCommand().isExit()); + + } + + public void tearDown() { + System.setOut(stdout); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/mindmymoney/command/CalculateInputCommandTest.java b/src/test/java/seedu/mindmymoney/command/CalculateInputCommandTest.java new file mode 100644 index 0000000000..3734043e1c --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/CalculateInputCommandTest.java @@ -0,0 +1,248 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.User; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +class CalculateInputCommandTest { + private final ByteArrayOutputStream capturedOut = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + + public void setUp() { + System.setOut(new PrintStream(capturedOut)); + } + + /** + * Asserts if user is able to calculate expenditure by date. + */ + @Test + void calculateInputCommand_calculateByDate_expectCorrectOutput() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Food /d Coke /a 20 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Entertainment /d Movie /a 10 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Personal /d Nike Shoes /a 200 /t 30/03/2021"; + new AddCommand(inputString, user).executeCommand(); + + setUp(); + new CalculateInputCommand("/epm 30/03/2022", user).executeCommand(); + tearDown(); + String expectedOutput = "Total expenditure in 30/03/2022 is $330.00." + System.lineSeparator() + + System.lineSeparator() + "BREAKDOWN OF EXPENSES:" + System.lineSeparator() + + "-----------------------------------------------" + System.lineSeparator() + + "FOOD: $$$$ [6.06%]" + System.lineSeparator() + + "TRANSPORT: [0.0%]" + System.lineSeparator() + + "UTILITIES: [0.0%]" + System.lineSeparator() + + "PERSONAL: $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ [90.91%]" + System.lineSeparator() + + "ENTERTAINMENT: $$ [3.03%]" + System.lineSeparator() + + "OTHERS: [0.0%]" + System.lineSeparator() + + "-----------------------------------------------"; + assertEquals(expectedOutput, capturedOut.toString().trim()); + } + + /** + * Asserts if user is able to calculate expenditure by month. + */ + @Test + void calculateInputCommand_calculateByMonth_expectCorrectOutput() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + inputString = "/e /pm cash /c Food /d Coke /a 20 /t 01/04/2022"; + + new AddCommand(inputString, user).executeCommand(); + + setUp(); + new CalculateInputCommand("/epm 03/2022", user).executeCommand(); + tearDown(); + String expectedOutput = "Total expenditure in 03/2022 is $300.00." + System.lineSeparator() + + System.lineSeparator() + "BREAKDOWN OF EXPENSES:" + System.lineSeparator() + + "-----------------------------------------------" + System.lineSeparator() + + "FOOD: [0.0%]" + System.lineSeparator() + + "TRANSPORT: [0.0%]" + System.lineSeparator() + + "UTILITIES: [0.0%]" + System.lineSeparator() + + "PERSONAL: $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ [100.0%]" + System.lineSeparator() + + "ENTERTAINMENT: [0.0%]" + System.lineSeparator() + + "OTHERS: [0.0%]" + System.lineSeparator() + + "-----------------------------------------------"; + assertEquals(expectedOutput, capturedOut.toString().trim()); + } + + /** + * Asserts if user is able to calculate expenditure by Year. + */ + @Test + void calculateInputCommand_calculateByYear_expectCorrectOutput() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + inputString = "/e /pm cash /c Food /d Coke /a 20 /t 30/04/2021"; + + new AddCommand(inputString, user).executeCommand(); + + setUp(); + new CalculateInputCommand("/epm 2022", user).executeCommand(); + tearDown(); + String expectedOutput = "Total expenditure in 2022 is $300.00." + System.lineSeparator() + + System.lineSeparator() + "BREAKDOWN OF EXPENSES:" + System.lineSeparator() + + "-----------------------------------------------" + System.lineSeparator() + + "FOOD: [0.0%]" + System.lineSeparator() + + "TRANSPORT: [0.0%]" + System.lineSeparator() + + "UTILITIES: [0.0%]" + System.lineSeparator() + + "PERSONAL: $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ [100.0%]" + System.lineSeparator() + + "ENTERTAINMENT: [0.0%]" + System.lineSeparator() + + "OTHERS: [0.0%]" + System.lineSeparator() + + "-----------------------------------------------"; + assertEquals(expectedOutput, capturedOut.toString().trim()); + } + + /** + * Asserts if user is able to use an incorrect flag. + */ + @Test + void calculateInputCommand_wrongFlag_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Personal /d Nike Shoes /a 3000 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("/a 30/03/2022", user).executeCommand()); + } + + /** + * Asserts if user is able to input an empty flag. + */ + @Test + void calculateInputCommand_emptyFlag_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Personal /d Nike Shoes /a 3000 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("30/03/2022", user).executeCommand()); + } + + /** + * Asserts if user is able to input an improper input. + */ + @Test + void calculateInputCommand_wrongInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Personal /d Nike Shoes /a 3000 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("30/03/2022", user).executeCommand()); + } + + /** + * Asserts if user is able to input an improper date input. + */ + @Test + void calculateInputCommand_wrongDateInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + inputString = "/e /pm cash /c Personal /d Nike Shoes /a 3000 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("/epm 34/03/2022", user).executeCommand()); + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("/epm 30/2022", user).executeCommand()); + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("/epm /2022", user).executeCommand()); + } + + /** + * Asserts if user is able to calculate from an empty list. + */ + @Test + void calculateInputCommand_emptyList_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("30/03/2022", user).executeCommand()); + } + + /** + * Asserts if user is able to add in a flag without date. + */ + @Test + void calculateInputCommand_noDate_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + assertThrows(MindMyMoneyException.class, + () -> new CalculateInputCommand("/epm", user).executeCommand()); + } + + /** + * Asserts if command is able to exit. + */ + @Test + void calculateInputCommand_isExit_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/epm 29/02/2020"; + + assertEquals(false, new CalculateInputCommand(inputString, user).isExit()); + + } + + public void tearDown() { + System.setOut(stdout); + } +} diff --git a/src/test/java/seedu/mindmymoney/command/DeleteCommandTest.java b/src/test/java/seedu/mindmymoney/command/DeleteCommandTest.java new file mode 100644 index 0000000000..df4b4aee86 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/DeleteCommandTest.java @@ -0,0 +1,345 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.Income; +import seedu.mindmymoney.userfinancial.User; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; + +/** + * Represents the tests for delete command. + */ +class DeleteCommandTest { + + /** + * Asserts that the correct item has been deleted. + * + * @throws MindMyMoneyException if incorrect item has been deleted. + */ + @Test + void deleteCommand_oneExpenditureInput_expectExpenditureListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + + ArrayList testList = new ArrayList<>(); + testList.add(new Expenditure("cash", "Personal", "Nike Shoes", + 300, "30/03/2022")); + + String deleteInputString = "delete /e 1"; + new DeleteCommand(deleteInputString, user).executeCommand(); + testList.remove(0); + + assertEquals(testList.size(), expenditureTestList.size()); + } + + /** + * Asserts that the credit card balance will be updated upon successful deletion. + * + * @throws MindMyMoneyException if incorrect item has been deleted. + */ + @Test + void deleteCommand_creditCardAndExpenditureInput_expectOriginalCreditBalance() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringCC = "/cc /n DBS /cb 1.5 /cl 500"; + new AddCommand(inputStringCC, user).executeCommand(); + String inputStringExpenditure = "/e /pm DBS /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + assertEquals(200.0, creditCardTestList.get(0).getBalanceLeft()); + String deleteInputString = "delete /e 1"; + new DeleteCommand(deleteInputString, user).executeCommand(); + assertEquals(500.0, creditCardTestList.get(0).getBalanceLeft()); + } + + /** + * Asserts that the correct item has been deleted. + */ + @Test + void deleteCommand_oneCreditCardInput_expectCreditCardListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/cc /n DBS /cb 1.5 /cl 500"; + new AddCommand(inputString, user).executeCommand(); + + ArrayList testList = new ArrayList<>(); + testList.add(new CreditCard("DBS", 1.5, 500)); + + String deleteInputString = "delete /cc 1"; + new DeleteCommand(deleteInputString, user).executeCommand(); + testList.remove(0); + + assertEquals(testList.size(), expenditureTestList.size()); + } + + /** + * Asserts that the correct income entry is deleted. + * + * @throws MindMyMoneyException when an invalid command is received. + */ + @Test + void deleteCommand_oneIncomeInput_expectIncomeListUpdated() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/i /a 3000 /c salary"; + new AddCommand(inputString, user).executeCommand(); + + ArrayList testList = new ArrayList<>(); + testList.add(new Income(3000, "Salary")); + + String deleteInputString = "delete /i 1"; + new DeleteCommand(deleteInputString, user).executeCommand(); + testList.remove(INDEX_OF_FIRST_ITEM); + + assertEquals(testList.size(), expenditureTestList.size()); + } + + /** + * Asserts if the index input is out of bounds. + */ + @Test + void deleteCommand_wrongExpenditureInputValue_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + String deleteInputString = "delete /e -1"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + String delInputString2 = "delete /e 5"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(delInputString2, user).executeCommand()); + } + + /** + * Asserts if the index input is in the correct number format. + */ + @Test + void deleteCommand_wrongExpenditureInputFormat_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + String delInputString = "delete /e ONE"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(delInputString, user).executeCommand()); + } + + /** + * Asserts if there is a missing index input in the command. + */ + @Test + void deleteCommand_missingExpenditureInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + + new AddCommand(inputString, user).executeCommand(); + String deleteInputString = "delete /e"; + String deleteInputString2 = "delete /e"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString2, user).executeCommand()); + } + + /** + * Asserts if user is able to delete from an empty expenditure list. + */ + @Test + void deleteCommand_deleteFromEmptyExpenditureList_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String deleteInputString = "delete /e 1"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if user is able to delete from an empty credit card list. + */ + @Test + void deleteCommand_deleteFromEmptyCreditCardList_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String deleteInputString = "delete /cc 1"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if user is able to delete from an empty income list. + */ + @Test + void deleteCommand_deleteFromEmptyIncomeList_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String deleteInputString = "delete /i 1"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if there is a missing index input in the command. + */ + @Test + void deleteCommand_missingCreditCardInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/cc /n DBS /cb 1.5 /cl 500"; + + new AddCommand(inputString, user).executeCommand(); + String deleteInputString = "delete /cc"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if there is a missing index input in the command. + */ + @Test + void deleteCommand_missingIncomeInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String deleteInputString = "delete /i"; + String inputString = "/i /a 3000 /c salary"; + new AddCommand(inputString, user).executeCommand(); + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if the credit card index input is out of bounds. + */ + @Test + void deleteCommand_wrongCreditCardInputValue_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/cc /n DBS /cb 1.5 /cl 500"; + + new AddCommand(inputString, user).executeCommand(); + String deleteInputString = "delete /cc -1"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + String delInputString2 = "delete /cc 3"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(delInputString2, user).executeCommand()); + } + + /** + * Asserts if the credit card index input can be non-numerical. + */ + @Test + void deleteCommand_nonNumericalCreditCardIndexInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/cc /n DBS /cb 1.5 /cl 500"; + + new AddCommand(inputString, user).executeCommand(); + String deleteInputString = "delete /cc asd"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if the income index input is out of bounds. + */ + @Test + void deleteCommand_wrongIncomeInputValue_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/i /a 3000 /c salary"; + + new AddCommand(inputString, user).executeCommand(); + String deleteInputString = "delete /i -1"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + String delInputString2 = "delete /i 3"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(delInputString2, user).executeCommand()); + } + + /** + * Asserts if there is a missing flag in the command. + */ + @Test + void deleteCommand_missingFlag_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String deleteInputString = "delete"; + + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Asserts if the income index input can be not a number. + */ + @Test + void deleteCommand_nonNumericalIncomeIndexInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/i /a 3000 /c salary"; + new AddCommand(inputString, user).executeCommand(); + + String deleteInputString = "delete /i abc"; + assertThrows(MindMyMoneyException.class, () -> new DeleteCommand(deleteInputString, user).executeCommand()); + } + + /** + * Test if program is able to exit. + */ + @Test + void deleteCommand_isExit_expectFalse() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputString = "/i /a 3000 /c salary"; + new AddCommand(inputString, user).executeCommand(); + + String deleteInputString = "delete /i 1"; + assertEquals(false, new DeleteCommand(deleteInputString, user).isExit()); + + } + +} diff --git a/src/test/java/seedu/mindmymoney/command/HelpCommandTest.java b/src/test/java/seedu/mindmymoney/command/HelpCommandTest.java new file mode 100644 index 0000000000..c1f288f5a0 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/HelpCommandTest.java @@ -0,0 +1,163 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Represents the tests for Help command. + */ +public class HelpCommandTest { + private final ByteArrayOutputStream capturedOut = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(capturedOut)); + } + + /** + * Asserts that the expenditure help page will be printed when the user requests for it. + */ + @Test + void helpCommand_fromUser_expectExpenditureHelpPage() throws MindMyMoneyException { + String helpPage = "---------------------------------------Expenditure Help Page------------------------" + + "---------------\n" + + "1. Listing all Expenditures: list /e {DATE}\n" + + "2. Adding an Expenditure entry: add /e /pm [PAYMENT_METHOD] /c [CATEGORY] " + + "/d [DESCRIPTION] /a [AMOUNT] /t [DATE]\n" + + "3. Calculating the total expenditure in a month: calculate /epm [DATE]\n" + + "4. Updating an Expenditure: update /e [NEW_INDEX] /pm [NEW_PAYMENT_METHOD] /c [NEW_CATEGORY] " + + "/d [NEW_DESCRIPTION] /a [NEW_AMOUNT] /t [NEW_DATE]\n" + + "5. Removing an Expenditure entry: delete /e [INDEX]\n" + + "6. Exiting the program: bye\n" + + "----------------------------------------------------------------------------------------------" + + "-----\n"; + + new HelpCommand(true, "/e").executeCommand(); + assertEquals(helpPage.trim(), capturedOut.toString().trim()); + } + + /** + * Asserts that the error message will be printed if an invalid command is received. + */ + @Test + void helpCommand_notFromUser_expectErrorMessage() throws MindMyMoneyException { + String errorMessage = "Invalid command! \n" + + "Type \"help /e\" to view the list of supported expenditure commands\n" + + "Type \"help /cc\" to view the list of supported Credit Card commands\n" + + "Type \"help /i\" to view the list of supported income commands\n"; + + new HelpCommand(false, "/e").executeCommand(); + assertEquals(errorMessage.trim(), capturedOut.toString().trim()); + } + + /** + * Asserts that the income help page will be printed when the user requests for it. + */ + @Test + void helpCommand_incomeFlag_expectIncomeHelpPage() throws MindMyMoneyException { + String helpPage = "--------------------------------Income Help Page------------------------------" + + "---------\n" + + "1. Listing all Incomes: list /i\n" + + "2. Adding an Income entry: add /i /a [AMOUNT] /c [CATEGORY]\n" + + "3. Updating an Income entry: update /i [INDEX] /a [NEW_AMOUNT] /c [NEW_CATEGORY]\n" + + "4. Removing an Income entry: delete /i [INDEX]\n" + + "---------------------------------------------------------------------------------------\n"; + + new HelpCommand(true, "/i").executeCommand(); + assertEquals(helpPage.trim(), capturedOut.toString().trim()); + } + + /** + * Asserts that the credi card help page will be printed when the user requests for it. + */ + @Test + void helpCommand_creditCardFlag_expectCreditHelpPage() throws MindMyMoneyException { + String helpPage = "---------------------------------------Credit Card Help Page--------------------------" + + "-------------\n" + + "1. Listing all Credit Cards: list /cc\n" + + "2. Adding a Credit Card: add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CREDIT_LIMIT]\n" + + "3. Updating a Credit Card: update /cc [INDEX] /n [NEW_NAME] /cb [NEW_CASHBACK] " + + "/cl [NEW_CREDIT_LIMIT]\n" + + "4. Removing a credit card: delete /cc [INDEX]\n" + + "5. Exiting the program: bye\n" + + "-----------------------------------------------------------------------------------------------" + + "----\n"; + + new HelpCommand(true, "/cc").executeCommand(); + assertEquals(helpPage.trim(), capturedOut.toString().trim()); + } + + /** + * Asserts that the error message will be printed when incorrect flag is provided. + */ + @Test + void helpCommand_invalidFlag_expectException() throws MindMyMoneyException { + assertThrows(MindMyMoneyException.class, + () -> new HelpCommand(true, "/abc").executeCommand()); + } + + /** + * Test if program is able to exit. + */ + @Test + void helpCommand_isExit_expectFalse() { + assertEquals(false, new HelpCommand(true, "").isExit()); + + } + + /** + * Asserts that the all help pages will be printed when the user requests for it. + */ + @Test + void helpCommand_fromUser_expectAllHelpPages() throws MindMyMoneyException { + String helpPage = "---------------------------------------Expenditure Help Page------------------------" + + "---------------\n" + + "1. Listing all Expenditures: list /e {DATE}\n" + + "2. Adding an Expenditure entry: add /e /pm [PAYMENT_METHOD] /c [CATEGORY] /d [DESCRIPTION] " + + "/a [AMOUNT] /t [DATE]\n" + + "3. Calculating the total expenditure in a month: calculate /epm [DATE]\n" + + "4. Updating an Expenditure: update /e [NEW_INDEX] /pm [NEW_PAYMENT_METHOD] /c [NEW_CATEGORY] " + + "/d [NEW_DESCRIPTION] /a [NEW_AMOUNT] /t [NEW_DATE]\n" + + "5. Removing an Expenditure entry: delete /e [INDEX]\n" + + "6. Exiting the program: bye\n" + + "----------------------------------------------------------------------------------------------" + + "-----\n" + + System.lineSeparator() + + "---------------------------------------Credit Card Help Page--------------------------" + + "-------------\n" + + "1. Listing all Credit Cards: list /cc\n" + + "2. Adding a Credit Card: add /cc /n [CREDIT_CARD_NAME] /cb [CASHBACK] /cl [CREDIT_LIMIT]\n" + + "3. Updating a Credit Card: update /cc [INDEX] /n [NEW_NAME] /cb [NEW_CASHBACK] " + + "/cl [NEW_CREDIT_LIMIT]\n" + + "4. Removing a credit card: delete /cc [INDEX]\n" + + "5. Exiting the program: bye\n" + + "-----------------------------------------------------------------------------------------------" + + "----\n" + + System.lineSeparator() + + "--------------------------------Income Help Page------------------------------" + + "---------\n" + + "1. Listing all Incomes: list /i\n" + + "2. Adding an Income entry: add /i /a [AMOUNT] /c [CATEGORY]\n" + + "3. Updating an Income entry: update /i [INDEX] /a [NEW_AMOUNT] /c [NEW_CATEGORY]\n" + + "4. Removing an Income entry: delete /i [INDEX]\n" + + "---------------------------------------------------------------------------------------\n" + + System.lineSeparator(); + + new HelpCommand(true, "").executeCommand(); + assertEquals(helpPage.trim(), capturedOut.toString().trim()); + } + + @AfterEach + public void tearDown() { + System.setOut(stdout); + } +} diff --git a/src/test/java/seedu/mindmymoney/command/ListCommandTest.java b/src/test/java/seedu/mindmymoney/command/ListCommandTest.java new file mode 100644 index 0000000000..9637a81ff3 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/ListCommandTest.java @@ -0,0 +1,331 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.User; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ListCommandTest { + private final ByteArrayOutputStream capturedOut = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + public void setUp() { + System.setOut(new PrintStream(capturedOut)); + } + + /** + * Tests list command with no date on a non-empty list. Prints list of size 1 first, + * followed by list of size 2, to check for formatting. + */ + @Test + void listToString_normalInputsWithNoDate_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String firstInputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(firstInputString, user).executeCommand(); + String listInString = new ListCommand("/e", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + String secondInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 30/03/2022"; + new AddCommand(secondInputString, user).executeCommand(); + listInString = new ListCommand("/e", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "2. $69.00 was spent on Cream Pie(Food) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + String listInString2 = new ListCommand("/e ", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "2. $69.00 was spent on Cream Pie(Food) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString2); + } + + /** + * Tests list command with exact date, month, year on a non-empty list. Prints list of size 1 first, + * followed by list of size 2, to check for formatting. + */ + @Test + void listToString_normalInputsWithExactDate_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String firstInputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(firstInputString, user).executeCommand(); + String listInString = new ListCommand("/e 30/03/2022", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + String secondInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 30/03/2022"; + new AddCommand(secondInputString, user).executeCommand(); + + String inputString3 = "/e /pm cash /c Food /d Cream Pie /a 69 /t 01/04/2022"; + + new AddCommand(inputString3, user).executeCommand(); + listInString = new ListCommand("/e 30/03/2022", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "2. $69.00 was spent on Cream Pie(Food) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + } + + /** + * Tests list command with exact month, year on a non-empty list. Prints list of size 1 first, + * followed by list of size 2, to check for formatting. + */ + @Test + void listToString_normalInputsWithExactMonth_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + String listInString = new ListCommand("/e 03/2022", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + String secondInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 30/03/2022"; + new AddCommand(secondInputString, user).executeCommand(); + + String thirdInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 01/04/2022"; + + new AddCommand(thirdInputString, user).executeCommand(); + listInString = new ListCommand("/e 03/2022", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "2. $69.00 was spent on Cream Pie(Food) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + } + + /** + * Tests list command with exact year on a non-empty list. Prints list of size 1 first, + * followed by list of size 2, to check for formatting. + */ + @Test + void listToString_normalInputsWithExactYear_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String inputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputString, user).executeCommand(); + String listInString = new ListCommand("/e 2022", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + String secondInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 30/03/2022"; + new AddCommand(secondInputString, user).executeCommand(); + + String thirdInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 01/04/2021"; + + new AddCommand(thirdInputString, user).executeCommand(); + listInString = new ListCommand("/e 2022", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "2. $69.00 was spent on Cream Pie(Food) using Cash [30/03/2022]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + } + + /** + * Tests list command for expenditure on an empty list. Should expect an exception thrown. + */ + @Test + void listCommand_emptyList_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + ListCommand listCommandTest = new ListCommand("/e", user); + + assertThrows(MindMyMoneyException.class, () -> listCommandTest.executeCommand()); + } + + /** + * Tests list command for date, month and year on an empty list. Should expect an exception thrown. + */ + @Test + void listCommand_wrongDateFormat_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + ListCommand listCommandTest = new ListCommand("/e 39/14/2022", user); + + assertThrows(MindMyMoneyException.class, () -> listCommandTest.executeCommand()); + } + + /** + * Tests list command for month and year on an empty list. Should expect an exception thrown. + */ + @Test + void listCommand_wrongMonthFormat_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + ListCommand listCommandTest = new ListCommand("/e 4/2022", user); + + assertThrows(MindMyMoneyException.class, () -> listCommandTest.executeCommand()); + } + + /** + * Tests list command for year on an empty list. Should expect an exception thrown. + */ + @Test + void listCommand_wrongYearFormat_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + ListCommand listCommandTest = new ListCommand("/e /2022/", user); + + assertThrows(MindMyMoneyException.class, () -> listCommandTest.executeCommand()); + } + + /** + * Test if program is able to exit. Should return false. + */ + @Test + void listCommand_isExit_expectFalse() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + assertEquals(false, new ListCommand("/e", user).isExit()); + + } + + /** + * Asserts if user is able to list command for credit card flag input. + */ + @Test + void listCommand_normalCreditCardInputs_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String firstInputString = "/cc /n DBS /cb 1.5 /cl 1000"; + new AddCommand(firstInputString, user).executeCommand(); + String secondInputString = "/cc /n POSB /cb 1.5 /cl 20"; + new AddCommand(secondInputString, user).executeCommand(); + setUp(); + new ListCommand("/cc", user).executeCommand(); + tearDown(); + String expectedOutput = "-----------------------------------------------" + + System.lineSeparator() + + "1. Name: DBS [Cashback: 1.50%] [Cashback gained: $0.00] [Card limit: $1000.00] " + + "[Balance left: $1000.00]\n" + + "2. Name: POSB [Cashback: 1.50%] [Cashback gained: $0.00] [Card limit: $20.00] [Balance left: $20.00]\n" + + "-----------------------------------------------"; + assertEquals(expectedOutput.trim(), capturedOut.toString().trim()); + } + + /** + * Tests list command for credit card flag input with empty list. Should expect an exception thrown. + */ + @Test + void listCommand_emptyListCreditCardInput_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + assertThrows(MindMyMoneyException.class, + () -> new ListCommand("/c", user).printCreditCardList()); + + } + + /** + * Tests list command for income flag input with empty list. Should expect an exception thrown. + */ + @Test + void listCommand_emptyListIncomeInput_expectException() { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + assertThrows(MindMyMoneyException.class, + () -> new ListCommand("/i", user).printIncomeList()); + + } + + /** + * Asserts if user is able to list command for income flag input. Should expect a print message. + */ + @Test + void listCommand_normalIncomeInput_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String firstInputString = "/i /a 3000 /c Salary"; + new AddCommand(firstInputString, user).executeCommand(); + String secondInputString = "/i /a 300 /c Salary"; + new AddCommand(secondInputString, user).executeCommand(); + setUp(); + new ListCommand("/i", user).executeCommand(); + tearDown(); + String expectedOutput = "-----------------------------------------------" + + System.lineSeparator() + + "1. Amount: $3000\n" + + " Category: Salary\n" + + "2. Amount: $300\n" + + " Category: Salary\n" + + "-----------------------------------------------" + + System.lineSeparator(); + assertEquals(expectedOutput.trim(), capturedOut.toString().trim()); + } + + /** + * Tests user is able to list the expenditure list. Should print out expenditure list. + */ + @Test + void listCommand_normalExpenditureInput_expectString() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + + String firstInputString = "/e /pm cash /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(firstInputString, user).executeCommand(); + String secondInputString = "/e /pm cash /c Food /d Cream Pie /a 69 /t 30/03/2021"; + new AddCommand(secondInputString, user).executeCommand(); + setUp(); + new ListCommand("/e", user).executeCommand(); + tearDown(); + String expectedOutput = "-----------------------------------------------" + + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using Cash [30/03/2022]\n" + + "2. $69.00 was spent on Cream Pie(Food) using Cash [30/03/2021]\n" + + "-----------------------------------------------" + + System.lineSeparator(); + assertEquals(expectedOutput.trim(), capturedOut.toString().trim()); + } + + public void tearDown() { + System.setOut(stdout); + } +} diff --git a/src/test/java/seedu/mindmymoney/command/UpdateCommandTest.java b/src/test/java/seedu/mindmymoney/command/UpdateCommandTest.java new file mode 100644 index 0000000000..eba4beffc3 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/command/UpdateCommandTest.java @@ -0,0 +1,344 @@ +package seedu.mindmymoney.command; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.Income; +import seedu.mindmymoney.userfinancial.User; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; + +public class UpdateCommandTest { + /** + * Assert that the command can update the expenditure list. + */ + @Test + void updateExpenditureCommand_updateExpenditure_listUpdated() { + Expenditure testExpenditure = new Expenditure("Cash", "Food", + "porridge", 5, "01/04/2022"); + Expenditure newExpenditure = new Expenditure("Cash", "Others", + "chicken rice", (float) 4.50, "01/05/2021"); + User testUser = new User(); + testUser.setExpenditureListArray(new ExpenditureList()); + testUser.getExpenditureListArray().add(testExpenditure); + String input = "/e 1 /pm cash /c Others /d chicken rice /a 4.50 /t 01/05/2021"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + try { + updateCommand.executeCommand(); + assertEquals(testUser.getExpenditureListArray().get(INDEX_OF_FIRST_ITEM), + newExpenditure); + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + fail(); + } + } + + /** + * Assert that an invalid update expenditure command will throw an exception. + */ + @Test + void updateExpenditureCommand_invalidInput_exceptionThrown() { + Expenditure testExpenditure = new Expenditure("Cash", "Food", + "porridge", 5, "01/03/2022"); + User testUser = new User(); + testUser.setExpenditureListArray(new ExpenditureList()); + testUser.getExpenditureListArray().add(testExpenditure); + String input = "invalid input"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an invalid update expenditure command will throw an exception. + */ + @Test + void updateExpenditureCommand_invalidDate_exceptionThrown() { + Expenditure testExpenditure = new Expenditure("Cash", "Food", + "porridge", 5, "01/03/2022"); + User testUser = new User(); + testUser.setExpenditureListArray(new ExpenditureList()); + testUser.getExpenditureListArray().add(testExpenditure); + String firstInputString = "1 /pm cash /c Personal /d Nike Shoes /a 500 /t 01/4/2022"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(firstInputString, testUser).executeCommand()); + String secondInputString = "1 /pm cash /c Personal /d Nike Shoes /a 500 /t 04/2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(secondInputString, testUser).executeCommand()); + String thirdInputString = "1 /pm cash /c Personal /d Nike Shoes /a 500 /t 2022"; + + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(thirdInputString, testUser).executeCommand()); + + String fourthInputString = "1 /pm cash /c Personal /d Nike Shoes /a 500 /t 38/14/2022"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(fourthInputString, testUser).executeCommand()); + + String fifthInputString = "1 /pm cash /c Food /d Porridge /a 4.50 /t 31/11/2021"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(fifthInputString, testUser).executeCommand()); + + String sixthInputString = "1 /pm cash /c Food /d Porridge /a 4.50 /t 29/02/2021"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(sixthInputString, testUser).executeCommand()); + + String seventhInputString = "1 /pm cash /c Food /d Porridge /a 4.50 /t 30/02/2020"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(seventhInputString, testUser).executeCommand()); + + String eighthInputString = "1 /pm cash /c Food /d Porridge /a 4.50 /t 31/04/2020"; + assertThrows(MindMyMoneyException.class, + () -> new AddCommand(eighthInputString, testUser).executeCommand()); + } + + /** + * Assert that when the update command fields are similar to the expenditure in the list, an exception is thrown. + */ + @Test + void updateExpenditureCommand_updateFieldSimilarToExpenditureInList_exceptionThrown() { + Expenditure testExpenditure = new Expenditure("Cash", "Food", + "porridge", 5, "01/04/2022"); + User testUser = new User(); + testUser.setExpenditureListArray(new ExpenditureList()); + testUser.getExpenditureListArray().add(testExpenditure); + String input = "/e 1 /pm cash /c food /d porridge /a 5 /t 01/04/2022"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that the command can update the credit card list. + */ + @Test + void updateCreditCardCommand_updateCreditCard_listUpdated() { + CreditCard testCreditCard = new CreditCard("UOB", 2, 1000); + CreditCard newCreditCard = new CreditCard("DBS", 5, 2000); + User testUser = new User(); + testUser.setCreditCardListArray(new CreditCardList()); + testUser.getCreditCardListArray().add(testCreditCard); + String input = "/cc 1 /n DBS /cb 5 /cl 2000"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + try { + updateCommand.executeCommand(); + assertEquals(testUser.getCreditCardListArray().get(INDEX_OF_FIRST_ITEM), + newCreditCard); + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + fail(); + } + } + + /** + * Assert that an invalid update credit card command will throw an exception. + */ + @Test + void updateCreditCardCommand_invalidInput_exceptionThrown() { + CreditCard testCreditCard = new CreditCard("DBS", 2, 1000); + User testUser = new User(); + testUser.setCreditCardListArray(new CreditCardList()); + testUser.getCreditCardListArray().add(testCreditCard); + String input = "/cc invalid input"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that when the update command fields are similar to the credit card in the list, an exception is thrown. + */ + @Test + void updateCreditCardCommand_updateFieldSimilarToCreditCardInList_exceptionThrown() { + CreditCard testCreditCard = new CreditCard("DBS", 2, 1000); + User testUser = new User(); + testUser.setCreditCardListArray(new CreditCardList()); + testUser.getCreditCardListArray().add(testCreditCard); + String input = "/cc 1 /n DBS /cb 2 /cl 1000"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that when the update command credit card limit is smaller than total expenditure on card, + * an exception is thrown. + */ + @Test + void updateCreditCardCommand_newCreditCardLimitSmallerThanExpenditure_exceptionThrown() + throws MindMyMoneyException { + CreditCard testCreditCard = new CreditCard("DBS", 2, 1000); + User testUser = new User(); + testUser.setCreditCardListArray(new CreditCardList()); + testUser.getCreditCardListArray().add(testCreditCard); + String inputStringExpenditure = "/e /pm DBS /c Personal /d Nike Shoes /a 500 /t 28/02/2018"; + new AddCommand(inputStringExpenditure, testUser).executeCommand(); + String input = "/cc 1 /n DBS /cb 2 /cl 200"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that the command can update the income list. + */ + @Test + void updateIncomeCommand_updateIncome_listUpdated() { + Income testIncome = new Income(1000, "Salary"); + Income newIncome = new Income(3000, "Salary"); + User testUser = new User(); + testUser.setIncomeListArray(new IncomeList()); + testUser.getIncomeListArray().add(testIncome); + String input = "/i 1 /a 3000 /c salary"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + try { + updateCommand.executeCommand(); + assertEquals(testUser.getIncomeListArray().get(INDEX_OF_FIRST_ITEM), newIncome); + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + fail(); + } + } + + /** + * Assert that an invalid update income command will throw an exception. + */ + @Test + void updateIncomeCommand_invalidInput_exceptionThrown() { + Income testIncome = new Income(1000, "Salary"); + User testUser = new User(); + testUser.setIncomeListArray(new IncomeList()); + testUser.getIncomeListArray().add(testIncome); + String input = "/i invalid input"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an update expenditure command with invalid index will throw an exception. + */ + @Test + void updateIncomeCommand_invalidIndexExpenditure_exceptionThrown() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/e /pm cash /c Personal /d Nike Shoes /a 500 /t 28/02/2018"; + String inputStringUpdate = "/e 2 /pm cash /c Personal /d Nike Shoes /a 10 /t 28/02/2018"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + UpdateCommand updateCommand = new UpdateCommand(inputStringUpdate, user); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an update expenditure command with non-numerical amount will throw an exception. + */ + @Test + void updateIncomeCommand_nonNumericalAmountExpenditure_exceptionThrown() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/e /pm cash /c Personal /d Nike Shoes /a 500 /t 28/02/2018"; + String inputStringUpdate = "/e 1 /pm cash /c Personal /d Nike Shoes /a asd /t 28/02/2018"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + UpdateCommand updateCommand = new UpdateCommand(inputStringUpdate, user); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an update credit card command with invalid index will throw an exception. + */ + @Test + void updateIncomeCommand_invalidIndexCreditCard_exceptionThrown() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/cc /n DBS /cb 2 /cl 1000"; + String inputStringUpdate = "/cc 2 /n DBS /cb 2 /cl 13000"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + UpdateCommand updateCommand = new UpdateCommand(inputStringUpdate, user); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an update credit card command with non-numerical Cashback will throw an exception. + */ + @Test + void updateIncomeCommand_nonNumericalCashback_exceptionThrown() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/cc /n DBS /cb 2 /cl 1000"; + String inputStringUpdate = "/cc 1 /n DBS /cb asd /cl 13000"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + UpdateCommand updateCommand = new UpdateCommand(inputStringUpdate, user); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an update income command with invalid index will throw an exception. + */ + @Test + void updateIncomeCommand_invalidIndexIncome_exceptionThrown() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/i /a 1000 /c salary"; + String inputStringUpdate = "/i 2 /a 100 /c salary"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + UpdateCommand updateCommand = new UpdateCommand(inputStringUpdate, user); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that an update income command with non numerical amount will throw an exception. + */ + @Test + void updateIncomeCommand_nonNumericalAmountIncome_exceptionThrown() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/i /a 1000 /c salary"; + String inputStringUpdate = "/i 1 /a asd /c salary"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + UpdateCommand updateCommand = new UpdateCommand(inputStringUpdate, user); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Assert that when the update command fields are similar to the income in the list, an exception is thrown. + */ + @Test + void updateIncomeCommand_updateFieldSimilarToIncomeInList_exceptionThrown() { + Income testIncome = new Income(1000, "Salary"); + User testUser = new User(); + testUser.setIncomeListArray(new IncomeList()); + testUser.getIncomeListArray().add(testIncome); + String input = "/i 1 /a 1000 /c salary"; + UpdateCommand updateCommand = new UpdateCommand(input, testUser); + assertThrows(MindMyMoneyException.class, updateCommand::executeCommand); + } + + /** + * Test if program is able to exit. + */ + @Test + void updateCommand_isExit_expectFalse() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringExpenditure = "/e /pm cash /c Personal /d Nike Shoes /a 500 /t 12/02/2018"; + String inputStringUpdate = "/e 1 /pm cash /c Personal /d Nike Shoes /a 500 /t 12/02/2022"; + new AddCommand(inputStringExpenditure, user).executeCommand(); + assertEquals(false, new UpdateCommand(inputStringUpdate, user).isExit()); + + } +} diff --git a/src/test/java/seedu/mindmymoney/helper/GeneralFunctionsTest.java b/src/test/java/seedu/mindmymoney/helper/GeneralFunctionsTest.java new file mode 100644 index 0000000000..fcfee4bdd4 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/helper/GeneralFunctionsTest.java @@ -0,0 +1,117 @@ +package seedu.mindmymoney.helper; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.command.AddCommand; +import seedu.mindmymoney.command.ListCommand; +import seedu.mindmymoney.data.CreditCardList; +import seedu.mindmymoney.data.ExpenditureList; +import seedu.mindmymoney.data.IncomeList; +import seedu.mindmymoney.userfinancial.CreditCard; +import seedu.mindmymoney.userfinancial.Expenditure; +import seedu.mindmymoney.userfinancial.User; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.mindmymoney.constants.ExpenditureFields.AMOUNT; +import static seedu.mindmymoney.constants.ExpenditureFields.CATEGORY; +import static seedu.mindmymoney.constants.ExpenditureFields.DESCRIPTION; +import static seedu.mindmymoney.constants.ExpenditureFields.EXPENDITURE; +import static seedu.mindmymoney.constants.ExpenditureFields.TIME; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_FIRST_ITEM; +import static seedu.mindmymoney.constants.Indexes.INDEX_OF_SECOND_ITEM; + +class GeneralFunctionsTest { + private final ByteArrayOutputStream capturedOut = new ByteArrayOutputStream(); + private final PrintStream stdout = System.out; + + public void setUp() { + System.setOut(new PrintStream(capturedOut)); + } + + /** + * Tests if findItemInList is able to search for the right fields. + */ + @Test + void generalFunction_findItemInList_expectItemFound() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + creditCardTestList.add(new CreditCard("dbs", 0.05, 50000)); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringOne = "/e /pm dbs /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputStringOne, user).executeCommand(); + String inputStringTwo = "/e /pm cash /c Food /d Shoes /a 30 /t 30/04/2021"; + new AddCommand(inputStringTwo, user).executeCommand(); + String listInString = new ListCommand("/e", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using dbs [30/03/2022]\n" + + "2. $30.00 was spent on Shoes(Food) using Cash [30/04/2021]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + // Tests if findItemInList searches for the right payment method + ArrayList result = GeneralFunctions.findItemsInList("Cash", + EXPENDITURE.toString(), expenditureTestList); + assertEquals(expenditureTestList.get(INDEX_OF_SECOND_ITEM), result.get(INDEX_OF_FIRST_ITEM)); + + // Tests if findItemInList searches for the right category + result = GeneralFunctions.findItemsInList("o", + CATEGORY.toString(), expenditureTestList); + assertEquals(expenditureTestList.get(INDEX_OF_FIRST_ITEM), result.get(INDEX_OF_FIRST_ITEM)); + + // Tests if findItemInList searches for the right description + result = GeneralFunctions.findItemsInList("Shoes", + DESCRIPTION.toString(), expenditureTestList); + assertEquals(expenditureTestList.get(INDEX_OF_FIRST_ITEM), result.get(INDEX_OF_FIRST_ITEM)); + assertEquals(expenditureTestList.get(INDEX_OF_SECOND_ITEM), result.get(INDEX_OF_SECOND_ITEM)); + + // Tests if findItemInList searches for the right amount + result = GeneralFunctions.findItemsInList("300", + AMOUNT.toString(), expenditureTestList); + assertEquals(expenditureTestList.get(INDEX_OF_FIRST_ITEM), result.get(INDEX_OF_FIRST_ITEM)); + + // Tests if findItemInList searches for the right time + result = GeneralFunctions.findItemsInList("30", + TIME.toString(), expenditureTestList); + assertEquals(expenditureTestList.get(INDEX_OF_FIRST_ITEM), result.get(INDEX_OF_FIRST_ITEM)); + assertEquals(expenditureTestList.get(INDEX_OF_SECOND_ITEM), result.get(INDEX_OF_SECOND_ITEM)); + } + + /** + * Tests if findItemInList is able to search with invalid input. + */ + @Test + void generalFunction_findItemInListInvalidInput_expectException() throws MindMyMoneyException { + ExpenditureList expenditureTestList = new ExpenditureList(); + CreditCardList creditCardTestList = new CreditCardList(); + IncomeList incomeList = new IncomeList(); + creditCardTestList.add(new CreditCard("dbs", 0.05, 50000)); + User user = new User(expenditureTestList, creditCardTestList, incomeList); + String inputStringOne = "/e /pm dbs /c Personal /d Nike Shoes /a 300 /t 30/03/2022"; + new AddCommand(inputStringOne, user).executeCommand(); + String inputStringTwo = "/e /pm cash /c Food /d Shoes /a 30 /t 30/04/2021"; + new AddCommand(inputStringTwo, user).executeCommand(); + String listInString = new ListCommand("/e", user).expenditureListToString(); + assertEquals("-----------------------------------------------" + System.lineSeparator() + + "1. $300.00 was spent on Nike Shoes(Personal) using dbs [30/03/2022]\n" + + "2. $30.00 was spent on Shoes(Food) using Cash [30/04/2021]\n" + + "-----------------------------------------------" + System.lineSeparator(), listInString); + + assertThrows(MindMyMoneyException.class, + () -> GeneralFunctions.findItemsInList("abc", + AMOUNT.toString(), expenditureTestList)); + + assertThrows(MindMyMoneyException.class, + () -> GeneralFunctions.findItemsInList("abc", + "abc", expenditureTestList)); + + assertThrows(MindMyMoneyException.class, + () -> GeneralFunctions.findItemsInList("abc", + DESCRIPTION.toString(), expenditureTestList)); + } + +} \ No newline at end of file diff --git a/src/test/java/seedu/mindmymoney/helper/PropertyListTest.java b/src/test/java/seedu/mindmymoney/helper/PropertyListTest.java new file mode 100644 index 0000000000..5cfff9ba63 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/helper/PropertyListTest.java @@ -0,0 +1,38 @@ +package seedu.mindmymoney.helper; + +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; +import seedu.mindmymoney.data.PropertyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** Tests for the PropertyList class. */ +public class PropertyListTest { + /** Asserts that PropertyList is able to properly serialize itself. */ + @Test + void propertyList_serialization_stringOutput() { + PropertyList plist = new PropertyList(); + plist.addProperty("test property", "test value"); + plist.addProperty("\"with \\quotes", "2.56"); + String serialized = plist.serialize(); + String expected = " \"test property\": \"test value\" " + + " \"\\\"with \\\\quotes\": \"2.56\" "; + assertEquals(expected, serialized); + } + + /** Asserts that PropertyList can properly deserialize itself. */ + @Test + void propertyList_deserialization_plistOutput() { + String serialized = " \"test property\": \"test value\" " + + " \"\\\"with \\\\quotes\": \"2.56\" "; + try { + PropertyList plist = PropertyList.deserialize(serialized); + assertEquals("test value", plist.getValue("test property")); + assertEquals("2.56", plist.getValue("\"with \\quotes")); + } catch (MindMyMoneyException e) { + System.out.println(e.getMessage()); + fail(); + } + } +} diff --git a/src/test/java/seedu/mindmymoney/userfinancial/ExpenditureTest.java b/src/test/java/seedu/mindmymoney/userfinancial/ExpenditureTest.java new file mode 100644 index 0000000000..cd33f054b8 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/userfinancial/ExpenditureTest.java @@ -0,0 +1,29 @@ +package seedu.mindmymoney.userfinancial; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import seedu.mindmymoney.MindMyMoneyException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Performs tests for Expenditures. + */ +public class ExpenditureTest { + /** + * Tests that an Expenditure can correctly serialize itself. + */ + @Test + void serialization_normalInput_correctSerialization() { + Expenditure expenditure = new Expenditure("Cash", "Food", "chicken" + + "expenditure", 20, "05/03/2022"); + try { + String serialized = expenditure.serialize(); + Expenditure deserialized = Expenditure.deserialize(serialized); + assertEquals(expenditure, deserialized); + } catch (MindMyMoneyException e) { + fail(); + } + } +} diff --git a/src/test/java/seedu/mindmymoney/userfinancial/IncomeTest.java b/src/test/java/seedu/mindmymoney/userfinancial/IncomeTest.java new file mode 100644 index 0000000000..8b7c86b7f4 --- /dev/null +++ b/src/test/java/seedu/mindmymoney/userfinancial/IncomeTest.java @@ -0,0 +1,5 @@ +package seedu.mindmymoney.userfinancial; + +class IncomeTest { + +} \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..e69de29bb2 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +0,0 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..c9fa9e5c38 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,7 @@ -James Gosling \ No newline at end of file +add /e /pm cash /c Food /d test /a 1234.681 /t +add /e /pm cash /c Food /d test /a 1234 /t (space) +add /e /pm cash /c Food /d test /a 1234 /t oko +add /e /pm cash /c Food /d test /a 1234 /t 1234 +add /e /pm cash /c Food /d \"); System.out.println("helo"); System.out.println("asd" /a 1234 /t 01/01/1900 +add /e /pm cash /c Food /d 欠扁 /a 1234 /t 01/01/1900 +add fuck this bug /e fuck this bug /pm cash /c Food /d Hello World /a 1234 /t 01/01/1900 \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 25ac7a2989..eb39997a15 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -12,8 +12,5 @@ for /f "tokens=*" %%a in ( set jarloc=%%a ) -java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TXT - -cd ..\..\text-ui-test - -FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! +REM remove tests as we adopt JUnit testing instead +ECHO Test passed! diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 1dcbd12021..9215bf5e69 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -8,16 +8,4 @@ cd .. cd text-ui-test -java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT - -cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix EXPECTED-UNIX.TXT ACTUAL.TXT -diff EXPECTED-UNIX.TXT ACTUAL.TXT -if [ $? -eq 0 ] -then - echo "Test passed!" - exit 0 -else - echo "Test failed!" - exit 1 -fi +echo "Test passed!" #Remove tests as we adopt JUnit testing instead