diff --git a/.gitignore b/.gitignore index 2873e189e1..307938f9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,18 @@ /out/ /*.iml +#Compiled class files +*.class + +#Logger files +logs +*.log + +#Generated storage text files +PromotionStorage.txt +StockMasterData.txt +TransactionLogs.txt + # Gradle build files /.gradle/ /build/ diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..5dea56cac4 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.StockMaster + diff --git a/README.md b/README.md index f82e2494b7..a179be9e90 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# Stock Master 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. @@ -35,11 +35,11 @@ Prerequisites: JDK 11 (use the exact version), update Intellij to the most recen ### I/O redirection tests -* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script. +* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-itemlist` and run the `runtest(.bat/.sh)` script. ### JUnit tests -* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template. +* A skeleton JUnit itemlist (`src/itemlist/java/seedu/duke/DukeTest.java`) is provided with this project template. * If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html). ## Checkstyle diff --git a/build.gradle b/build.gradle index ea82051fab..ca09fcd187 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("seedu.duke.StockMaster") } shadowJar { @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in -} + enableAssertions = true +} \ No newline at end of file diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..070671a1be 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 +--------|:--------:|:----------------------------------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Low Tjun Lym | [Github](https://github.com/LowTL) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Fu Xueer | [Github](https://github.com/fxe025) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Heng Shu Hong | [Github](https://github.com/HengShuHong) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Joel Lim | [Github](https://github.com/joellimjr) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Min Guanlin | [Github](https://github.com/Fureimi) | [Portfolio](docs/team/johndoe.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..06e6d3fc33 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,483 @@ # Developer Guide +## Table of contents + +* [Acknowledgements](#acknowledgements) +* [Design](#design) + * [Architecture Diagram](#architecture-diagram) + * [Command](#command) + * [Item](#item) + * [Itemlist](#itemlist) + * [Sequence Diagram](#sequence-diagram-of-listcommand-when-used-to-list-items) + * [Class Diagram](#class-diagram-of-editcommand-and-with-partial-class-diagrams-of-item-and-itemlist) + * [Parser](#parser) + * [Class Diagram](#class-diagram-of-parser) + * [Storage](#storage) + * [Class Diagram](#storage-class-diagram) + * [Sequence Diagram](#storage-sequence-diagram) + * [UI](#ui) + * [Cashier](#cashier-features) + * [Class Diagram](#cashier-class-diagram) + * [Sequence Diagrams](#cashier-sequence-diagrams) + * [Promotions](#promotion-feature) + * [Sequence Diagram](#promotion-sequence-diagram) + * [Add Promotion](#add-new-promotion) + * [Class Diagram](#add-promotion-class-diagram) + * [Sequence Diagram](#add-promotion-sequence-diagram) +* [Product Scope](#product-scope) +* [User Stories](#user-stories) +* [Non-Functional Requirements](#non-functional-requirements) +* [Glossary](#glossary) +* [Instructions for manual testing](#instructions-for-manual-testing) + ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +**DG Adapted from** + +* [Addressbook-Level3](https://github.com/se-edu/addressbook-level3) + +## Design + +### Architecture Diagram +![ArchitectureDiagram](Diagrams/Images/ArchitectureDiagram.png) + +The **Architecture Diagram** above explains the high-level design of the program. + +#### Main components of the architecture +`Main` is the `StockMaster` class, and controls the operation and closing of the app. + +The program consists of several components: +* `TextUi` handles interaction with the User +* `Parser` converts the user input into `Command` +* `Command` executes the commands given by the user +* `Itemlist`, `Cashier` and `PromotionList` are ArrayLists of type `Item`, `Transaction` and `Promotion` +respectively. +* `Storage` reads from and writes to the hard disk. + +### Command +The Command class is an abstract class which is extended to execute the various commands +used in the product. It contains the abstract method `execute`, which is overridden by all other Command child classes. + +### Item +Item class is an object which represents an item in the stock inventory list. Stores data about the item such as item +price, quantity of item, and others. + +### Itemlist +Itemlist class is an object which contains items to be added to the stock inventory list. Able to add items, +remove functions, edit items inside the list. + +To list items in the `Itemlist` to the user, the `ListCommand` class is used. + +#### Sequence Diagram of `ListCommand` when used to list items. + +![ListItems_SequenceDiagram](Diagrams/Images/Itemlist/ListItems_SequenceDiagram.png) +1. An instance of `ListCommand` is created with parameters specifying what category to list, or whether to only +list marked items, as well as an `Itemlist` class. +2. The `execute()` method is then called, checking for modifiers such as category or isListMarked. +3. Depending on the modifiers, different things will happen. + - If there are no modifiers, `ListCommand` will display all items in the list by calling `TextUi.ShowList()`. +4. If there are modifiers present, `ShowCustomizedItemList()` will be called. + - If there is a category present, `ListCommand` will get the category of every item in the `Itemlist` with + `item.getCategory()`. + - If isListMarked is true, `ListCommand` will get the mark status of evey item in the `Itemlist` with + `item.getMarkStatus()`. + - Afterwards, `TextUi.replyToUser()` will be called, displaying the relevant items. + + +The `AddCommand` class extends the `Command` class, allowing users to add items to the `Itemlist` + +![AddCommand_SequenceDiagram](Diagrams/Images/Itemlist/AddCommand_SequenceDiagram.png) + +The `EditCommand` class extends the `command` class. +The `EditCommand` is responsible for editing attributes of an item in the `Itemlist`. This includes changing the +item's name, quantity, unit of measurement, category, buy price, and sell price. +The command modifies the relevant item if it exists and updates the system accordingly. + +#### Class Diagram of `EditCommand` and with partial Class Diagrams of `item` and `Itemlist` + +![EditCommand_ClassDiagram](Diagrams/Images/Itemlist/EditCommand_ClassDiagram.png) + +#### Sequence Diagram of `EditCommand` +![EditCommand_SequenceDiagram](Diagrams/Images/Itemlist/EditCommand_SequenceDiagram.png) + +1. When an instance of `EditCommand` is created, the parameters indicating the item to be edited and the new values for +these parameters are also specified. +2. - Afterwards, the `execute()` method will be called, which first interacts with `Itemlist` class by calling +`Itemlist.getItems()` to retrieve the list of all items. + - If the item is found, it retreives the index of the item. If not, a messasge indicating that item is not found + will be displayed using `TextUi.replyToUser()`. +3. If an item is found, the `Itemlist.getItem(index)` method is used to retrieve the item object. For each +attribute that needs modification, the corresponding setter method on the `item` objet is called, such as +`setItemName()`, `setQuantity()`, etc. +4. Once all changes have been made, `TextUi.replyToUser()` is called to display to the user that the editing +process had concluded. +5. Finally, `Storage.overwriteFile(Itemlist.getItems())` is called to write changes to the local save file. + + +### Parser +Parser class processes user inputs and sieves out relevant details before calling the relevant methods. + +#### Class Diagram of `Parser` +![Parser_ClassDiagram](Diagrams/Images/Parser/Parser_ClassDiagram.png) +1. Parser takes in the user input, and parses out the command word. +2. According to the command word detected, it will check that the input matches the required command format, +throwing command format exceptions if it does not match. +3. It then checks if the inputs for the various parameters (i.e qty/, buy/, etc) are of the correct type and appropriate value, +throwing other exceptions accordingly. +4. It creates a new instance of the relevant command for it to execute() its code. + +### Storage +* Storage class contains method `addToFile()` to write data of items to the default file directory, `./StockMasterData.txt`. +* `overwriteFile()` write data of items to the default file directory, overwriting previous contents in the file. +* Method `readFromFile()` retrieve information from the file when program restarts. Information is used to create new `item` object, which is added to +the Itemlist by `addItem()` method. + +### Storage Class diagram +![Storage_ClassDiagram](Diagrams/Images/Storage/Storage_ClassDiagram.png) + +### Storage Sequence diagram + +![Storage_sequenceDiagram](Diagrams/Images/Storage/Storage_sequenceDiagram.png) + +### UI +UI prints command output, and useful messages to the user. + +### Cashier features +Cashier class extends Itemlist class, and stores `Transactions` instead of `Items`. +It has 4 main functions: `addItem`, `getTransactions`, `getBestseller` and `getTotalProfit`. +The main function of this class is to track transactions, as well as provide some basic +business analytics to the app. + +It mainly uses 4 `Command` subclasses, namely the `AddCommand`, `ListCommand`, `BestsellerCommand` and +`TotalProfitCommand`. Each `Command` subclass executes their respective function, and print the result through +`TextUi`. + +To improve the robustness of the program, the `Transaction` stores the `Item` sold as a `String` rather than an `Item`, +to allow for users to edit or delete the `Item` without losing the history of which `Item` was sold in the past. +This also allows for the analytics to work with `Items` that no longer exist. + +Note: To simulate the reality of business accounting, the `delete` command for `Transactions` are omitted on purpose. +In actual accounting software, transactions are not allowed to be deleted or edited to preserve its credibility. + +### Cashier Class Diagram +![CashierClassDiagram](Diagrams/Images/Cashier/CashierClassDiagram.png) + +Above is the class diagram for the `Cashier` class, and its dependencies. +The `Command` subclasses at the top fit their respective functions, and `SellCommand` maps to the `addItem` method. + +### Cashier Sequence Diagrams +![CashierSeqDiagram](Diagrams/Images/Cashier/CashierSequenceDiagram.png) + +BestsellerCommand: `getBestseller` ref from the overall sequence diagram above: +![BestsellerCommand](Diagrams/Images/Cashier/BestsellerCommand_SequenceDiagram.png) + +TotalProfitCommand: `getTotalProfit` and `getTotalRevenue` refs from the overall sequence diagram: +![TotalProfitCommand](Diagrams/Images/Cashier/TotalProfitCommand_SequenceDiagram.png) + +## Implementation + +This section describes some noteworthy details on how certain features are implemented + +### Promotion feature + +The promotion mechanism is facilitated by `StockMaster`. It enables the user to design and create discount offers for his/her +own business given a certain period and time. Additionally, it implements the following operations: +* `promotion` +* `del_promotion` +* `list_promotions` + + +Given below is the overall sequence diagram for the `PromotionCommand`. The reference frames are shown when explaining +the operations. + +#### Promotion Sequence Diagram +![PromotionSequenceDiagram](Diagrams/Images/Promotion/Promotion_SequenceDigram.png) + + +The PromotionCommand will execute the appropriate command and prints messages to the user through the `TextUi`. + +#### Add new promotion: + +The add promotion command has 5 compulsory arguments `ITEM_NAME`, `discount/`, `period /from`, `/to`, `time /from` and `to` + +Example: -## Design & implementation +``` +promotion apple discount/50 period /from 1 Jan 2024 /to 31 Dec 2024 time from/ 0000 /to 2359 +``` +#### Add Promotion Class Diagram + +Given below is the class diagram showing the class structure of the add promotion mechanism: + +![AddPromotion Class Diagram](Diagrams/Images/Promotion/AddPromotion_ClassDiagram.png) + +#### Add Promotion Sequence Diagram + +Given below is the sequence diagram showing the add promotion mechanism. + +![AddPromotion Sequence Diagram](Diagrams/Images/Promotion/AddPromotion.png) + +This command will add a new promotion by calling `addPromotion(promotion)` method in `Promotionlist.java`. The +`addPromotion(promotion)` then calls `isItemExist(apple)` in `Itemlist.java` to check if the item exists in the inventory. + + +Next, it subsequently calls multiple of its own methods. +1. `ItemIsOnPromo()` checks if there is already an existing `promotion` for the item. If there is an existing promotion +the user will be unable to create another promotion for the same item. +2. `isValidDiscount()` checks if the `discount` input lies between the range of 0 to 100. +3. `isValidMonth()` checks if the `date` entered is valid. E.g. `30 FEB 2024` does not exist. +4. `isValidTime()` checks if the time is a valid range. +5. `isValidDuration()` checks if the duration of the promotion is valid. E.g. A promotion that starts on `1 FEB 2024` and +ends on `1 JAN 2024` is not valid. + +The sequence diagram shows the successful creation of a promotion. However, if any of the `boolean` values do not follow +as per the diagram, an error message will be shown to the user via the `TextUi`. + +Then, `add(promotion)` method is called in `Promotion.java` to create the promotion. + +A response will then be printed to the `TextUi` to inform the user on the successful creation of the promotion. + +**Delete promotion:** + +This command has one compulsory argument `ITEM_NAME`. + +Example: +``` +del_promo apple +``` + +#### Delete Promotion Sequence Diagram + +Given below is the sequence diagram showing the delete promotion mechanism: + +![DeletePromotion](Diagrams/Images/Promotion/DeletePromotion.png) + +This command will initially check if there is such an item in `Promotionlist`. If it does not exist, it will print an +error message. Otherwise, it will execute the deletion of the `promotion`. + +To execute the deletion, `getPromotion()` and `getIndex()` methods are called to obtain the index of the item in the +`Promotionlist`. + +The promotion will be deleted by calling `deletePromotion(index)` method in `Promotionlist.java` and will inform the +user on the successful deletion of the promotion via the `TextUi`. + +**List promotion:** + +This command lists all the `promotion` in `Promotionlist`. + +Example: +``` +list_promotions +``` + +#### List Promotion Sequence Diagram + +![ListPromotion](Diagrams/Images/Promotion/ListPromotion_SequenceDigram.png) + +All of the `Promotions` will be shown to the user through the `TextUi`. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} ## Product scope + ### Target user profile -{Describe the target user profile} +Small Business Owners who: +* has a need to manage a significant number of inventory products +* able to track revenue/loss of the business +* set up promotions for the items +* needs reminders for items that are low on stock +* prefer desktop apps over other types +* can type fast +* prefers typing to mouse interactions +* is reasonably comfortable using CLI apps ### Value proposition -{Describe the value proposition: what problem does it solve?} +StockMaster helps small business owners organise and manage their business. The purpose of such application is to provide +users with a range of tools and features to help them better operate their business. This will enable them to make more +informed decisions to ensure that they are consistently having a profit. The application allows users to keep track of +their inventory, promotions and transaction logs. It also lets the user see the earnings/loss of the business. Furthermore, +it also allows users to see which item has generated the most profit in the business. + ## User Stories -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +| Version | As a ... | I want to ... | So that I can ... | +|---------|-------------|---------------------------------------------------------------|------------------------------------------------------------------------------| +| v1.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| v1.0 | user | add new items | update my inventory list | +| v1.0 | user | make changes to added items | change details about items such as quantity, price | +| v1.0 | user | delete item | remove items that are no longer required | +| v1.0 | user | search for specific item | easily check how much quantity I have left for that item | +| v1.0 | user | list out my inventory | view all items that I have | +| v2.0 | store owner | include new item information such as buying and selling price | operate my business and sell to customers | +| v2.0 | store owner | search for items in a filtered list | easily check the item information based on the filtered list | +| v2.0 | store owner | keep track of how much I spend | generate my overall expenditure | +| v2.0 | store owner | keep track of how much I earn | generate my overall revenue | +| v2.0 | store owner | get my overall profit | know if my business is earning or losing money | +| v2.0 | store owner | sell items | start earning money from my business | +| v2.0 | store owner | see reminders for items that are low on stock | easily know which item I have to schedule for a restock | +| v2.0 | store owner | add promotions for a time period | automatically change the sell price of the items during the promotion period | +| v2.0 | store owner | delete promotions | remove promotions when it is over | +| v2.0 | store owner | list promotions | view all promotions that I have created | +| v2.0 | store owner | mark items of different categories at my own discretion | easily view the list of marked items when I want to | +| v2.0 | store owner | see what is my best selling item | identify which item is most popular among customers | ## Non-Functional Requirements -{Give non-functional requirements} +* The application should work on main OS (Windows, Linux, Mac) that has Java 11 installed. +* The application is designed for a single user. +* This application is targeted towards users who have an above average typing speed. +* This application requires the user to have an accurate clock on the main OS. +* This application does not allow users to amend the text file that are used as storage. ## Glossary -* *glossary item* - Definition +* *CLI* - Command Line Interface, where the user types commands rather than clicking options. +* *item* - item to be sold at the shop, with key information such as quantity, buying/selling price, description etc. ## Instructions for manual testing +Note: These instructions only provide a starting point for testers to work on. + +For the most optimal testing, please follow the instructions section by section to test all the features. + +### Launch and shutdown +1. Initial launch + 1. Download the jar file and move it into an empty folder + 2. Open the terminal, change to the correct directory and run `java -jar [CS2113-T15-4][StockMaster].jar`. Expected outcome: + ``` + ---------------- + StockMaster v2.0 + ---------------- + Data is being extracted from: ./StockMasterData.txt + Welcome to StockMaster, where you can master the knowledge on your Stock! + Out-of-stock Items: + No items out of stock + Low-on-stock Items: (less than 10) + No items low on stock + Enter Command: + ``` +2. Closing the Application + 1. Type `exit` into the terminal. + 2. Expected outcome: + ``` + ---------------- + Inventory is being saved to :./StockMasterData.txt + ---------------- + Transactions are being saved to:./TransactionLogs.txt + ---------------- + Promotions are being saved to: ./PromotionStorage.txt + ---------------- + Thank you for using StockMaster, hope we have helped your lazy ass! + ``` + At this point, you should be able to see the logs folder for logging, as well as `StockMasterData.txt`, + `TransactionLogs.txt` and `PromotionStorage.txt` in the directory that the jar was in. + +### Adding test data +To test the rest of the features, there must be data to work on. By running several `add` commands, we can populate the +`Itemlist`, in order to work on them. You can use the following command: + +`add testItem qty/10 /ea buy/1.00 sell/2.00` + +You should see the following output: +``` +added: testItem (Qty: 10 ea Buy: $1.00 Sell: $2.00) +``` + +You can also use the following command to test optional arguments: + +`add testItem2 qty/10 /ea cat/testCat buy/1.00 sell/2.00` + +You should see the following output: +``` +added: testItem (Qty: 10 ea Buy: $1.00 Sell: $2.00) to testCat +``` + +### List Items +To test this feature, you can use the above 2 commands (in [Adding Test Data](#adding-test-data)) to populate the `Itemlist` first. + +Make sure that there are no existing `Storage` files, or `Item` in the program to get the exact outputs below. +You can clear the existing data by exiting the program (via `exit`) and deleting all the save files +(`StockMasterData.txt`, `TransactionLogs.txt`, `PromotionStorage.txt`) in the folder. + +After using the two lines above, you can test the following command: `list_items` + +Output: +``` +List: +1. [ ] testitem (Qty: 10 ea, Buy: $1.00, Sell: $2.00) +2. [ ] testitem2 (Qty: 10 ea, Buy: $1.00, Sell: $2.00, Category: testCat) +``` + +### Adding a new Transaction +To add a new transaction, the `sell` command is used: +Input: `sell testitem qty/5` +Output: +``` +Quantity of testitem sold: 5, for: $2.0 +Quantity remaining: 5 +Total value sold: 10.0 +``` + +### Adding a new promotion +Input: `promotion testitem discount/50 period /from 15 APR 2024 /to 31 DEC 2024 time /from 0000 /to 2359` +Output: +``` +The following promotion has been added +testitem have a 50.00% discount +Period: 15 APR 2024 to 31 DEC 2024 +Time: 0000 to 2359 +``` + +To see the current promotion in action, run the following command: +Input: `sell testitem qty/5` +Output: +``` +Quantity of testitem sold: 5, for: $1.0 +Quantity remaining: 5 +Total value sold: 5.0 +``` + +As seen above, there is a 50% discount applied to the selling price of `testitem` (from $2.0 to $1.0) + +### Viewing the promotion +Input: `list_promotion` +Output: +``` +1. testitem have a 50.00% discount +Period: 15 APR 2024 to 31 DEC 2024 +Time: 0000 to 2359 +``` + +### Deleting a promotion +Note: If there is a promotion for the item, users will not be allowed to delete the item until the promotion is deleted. +This is a safeguard against bloating the `PromotionStorage` file with unnecessary promotions. + +Input: `del_promo testitem` +Output: `Promotion for testitem has been removed` + +### Viewing the transactions +Input: `list_transactions` +Output: +``` +1. 5 testitem Sell: $2.0 Date: 2024-04-12 16:37:19 +2. 5 testitem Sell: $1.0 Date: 2024-04-12 16:41:24 +``` +Note: The date displayed will differ based on your system time. + +### Viewing the bestseller, total profits and total revenue +1. Bestseller input: `bestseller` + Expected outcome: `The current best-selling item is testitem.` +2. Total profit input: 'total_profit' + Expected outcome: `You have earned 5.0 in profits so far.` +3. Total revenue input: 'total_revenue' + Expected outcome: `You have earned 15.0 in revenue so far.` + +### Deleting an item +Input: `del testitem` +Output: `testitem has been successfully deleted.` -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +**Note**: If the item still has a promotion, the item cannot be deleted. +Input `del_promo testitem` If you receive the error `There is a promotion that exists for this item. Please remove +the promotion before deleting the item.` diff --git a/docs/Diagrams/Images/ArchitectureDiagram.png b/docs/Diagrams/Images/ArchitectureDiagram.png new file mode 100644 index 0000000000..3508d20b7c Binary files /dev/null and b/docs/Diagrams/Images/ArchitectureDiagram.png differ diff --git a/docs/Diagrams/Images/Cashier/BestsellerCommand_SequenceDiagram.png b/docs/Diagrams/Images/Cashier/BestsellerCommand_SequenceDiagram.png new file mode 100644 index 0000000000..49f67dee35 Binary files /dev/null and b/docs/Diagrams/Images/Cashier/BestsellerCommand_SequenceDiagram.png differ diff --git a/docs/Diagrams/Images/Cashier/CashierClassDiagram.png b/docs/Diagrams/Images/Cashier/CashierClassDiagram.png new file mode 100644 index 0000000000..f41c3acb9e Binary files /dev/null and b/docs/Diagrams/Images/Cashier/CashierClassDiagram.png differ diff --git a/docs/Diagrams/Images/Cashier/CashierSequenceDiagram.png b/docs/Diagrams/Images/Cashier/CashierSequenceDiagram.png new file mode 100644 index 0000000000..cc31a04a14 Binary files /dev/null and b/docs/Diagrams/Images/Cashier/CashierSequenceDiagram.png differ diff --git a/docs/Diagrams/Images/Cashier/TotalProfitCommand_SequenceDiagram.png b/docs/Diagrams/Images/Cashier/TotalProfitCommand_SequenceDiagram.png new file mode 100644 index 0000000000..4c8910f65d Binary files /dev/null and b/docs/Diagrams/Images/Cashier/TotalProfitCommand_SequenceDiagram.png differ diff --git a/docs/Diagrams/Images/Cashier/TransactionClassDiagram.png b/docs/Diagrams/Images/Cashier/TransactionClassDiagram.png new file mode 100644 index 0000000000..fd930bafd0 Binary files /dev/null and b/docs/Diagrams/Images/Cashier/TransactionClassDiagram.png differ diff --git a/docs/Diagrams/Images/Itemlist/AddCommand_SequenceDiagram.png b/docs/Diagrams/Images/Itemlist/AddCommand_SequenceDiagram.png new file mode 100644 index 0000000000..970784ed45 Binary files /dev/null and b/docs/Diagrams/Images/Itemlist/AddCommand_SequenceDiagram.png differ diff --git a/docs/Diagrams/Images/Itemlist/EditCommand_ClassDiagram.png b/docs/Diagrams/Images/Itemlist/EditCommand_ClassDiagram.png new file mode 100644 index 0000000000..3c6ccfedb0 Binary files /dev/null and b/docs/Diagrams/Images/Itemlist/EditCommand_ClassDiagram.png differ diff --git a/docs/Diagrams/Images/Itemlist/EditCommand_SequenceDiagram.png b/docs/Diagrams/Images/Itemlist/EditCommand_SequenceDiagram.png new file mode 100644 index 0000000000..1e7ccb6e71 Binary files /dev/null and b/docs/Diagrams/Images/Itemlist/EditCommand_SequenceDiagram.png differ diff --git a/docs/Diagrams/Images/Itemlist/ListItems_SequenceDiagram.png b/docs/Diagrams/Images/Itemlist/ListItems_SequenceDiagram.png new file mode 100644 index 0000000000..04242495a0 Binary files /dev/null and b/docs/Diagrams/Images/Itemlist/ListItems_SequenceDiagram.png differ diff --git a/docs/Diagrams/Images/Parser/Parser_ClassDiagram.png b/docs/Diagrams/Images/Parser/Parser_ClassDiagram.png new file mode 100644 index 0000000000..253cdf14dd Binary files /dev/null and b/docs/Diagrams/Images/Parser/Parser_ClassDiagram.png differ diff --git a/docs/Diagrams/Images/Promotion/AddPromotion.png b/docs/Diagrams/Images/Promotion/AddPromotion.png new file mode 100644 index 0000000000..8848725e26 Binary files /dev/null and b/docs/Diagrams/Images/Promotion/AddPromotion.png differ diff --git a/docs/Diagrams/Images/Promotion/AddPromotion_ClassDiagram.png b/docs/Diagrams/Images/Promotion/AddPromotion_ClassDiagram.png new file mode 100644 index 0000000000..f371d4c3d5 Binary files /dev/null and b/docs/Diagrams/Images/Promotion/AddPromotion_ClassDiagram.png differ diff --git a/docs/Diagrams/Images/Promotion/DeletePromotion.png b/docs/Diagrams/Images/Promotion/DeletePromotion.png new file mode 100644 index 0000000000..2bfc491e43 Binary files /dev/null and b/docs/Diagrams/Images/Promotion/DeletePromotion.png differ diff --git a/docs/Diagrams/Images/Promotion/ListPromotion_SequenceDigram.png b/docs/Diagrams/Images/Promotion/ListPromotion_SequenceDigram.png new file mode 100644 index 0000000000..c079699e72 Binary files /dev/null and b/docs/Diagrams/Images/Promotion/ListPromotion_SequenceDigram.png differ diff --git a/docs/Diagrams/Images/Promotion/Promotion_SequenceDigram.png b/docs/Diagrams/Images/Promotion/Promotion_SequenceDigram.png new file mode 100644 index 0000000000..1553fdaede Binary files /dev/null and b/docs/Diagrams/Images/Promotion/Promotion_SequenceDigram.png differ diff --git a/docs/Diagrams/Images/Storage/Storage_ClassDiagram.png b/docs/Diagrams/Images/Storage/Storage_ClassDiagram.png new file mode 100644 index 0000000000..3c1a885d78 Binary files /dev/null and b/docs/Diagrams/Images/Storage/Storage_ClassDiagram.png differ diff --git a/docs/Diagrams/Images/Storage/Storage_sequenceDiagram.png b/docs/Diagrams/Images/Storage/Storage_sequenceDiagram.png new file mode 100644 index 0000000000..96cb82c9c4 Binary files /dev/null and b/docs/Diagrams/Images/Storage/Storage_sequenceDiagram.png differ diff --git a/docs/Diagrams/PUML files/ArchitectureDiagram.puml b/docs/Diagrams/PUML files/ArchitectureDiagram.puml new file mode 100644 index 0000000000..cb267e1848 --- /dev/null +++ b/docs/Diagrams/PUML files/ArchitectureDiagram.puml @@ -0,0 +1,41 @@ +@startuml +!include Style.puml + +package "" as f { +Class TextUi #LightBlue +Class Parser #Magenta +Class Main #SkyBlue +Class Storage #Green +Class Command #Grey +Class Cashier #Orange +Class PromotionList #Gold +Class Itemlist #Tan +} + +Class User #IndianRed +file "Storage" as File #DarkOliveGreen { +} +file "TransactionLogs" as File2 #DarkOliveGreen { +} +file "PromotionStorage"" as File3 #DarkOliveGreen { +} + +User -d-> TextUi +TextUi --> Parser +TextUi <--> Main +TextUi <--> Storage +Main --> Storage +Main -l-> Parser +Main --> Command +Parser --> Command +Command --> Cashier +Command --> Itemlist +Command --> PromotionList +Itemlist <--> PromotionList +Itemlist <--> Cashier + +Storage <-d-> File +Storage <-r-> File2 +Storage <-d-> File3 + +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Cashier/BestsellerCommand_SequenceDiagram.puml b/docs/Diagrams/PUML files/Cashier/BestsellerCommand_SequenceDiagram.puml new file mode 100644 index 0000000000..64d549ed31 --- /dev/null +++ b/docs/Diagrams/PUML files/Cashier/BestsellerCommand_SequenceDiagram.puml @@ -0,0 +1,15 @@ +@startuml +!include ../Style.puml + +participant ":BestsellerCommand" as BestsellerCommand +participant ":Cashier" as Cashier +participant ":TextUi" as TextUi + +BestsellerCommand -> Cashier: getBestseller() +activate Cashier +Cashier -> Cashier: Retrieve bestseller +Cashier --> BestsellerCommand: itemName +deactivate Cashier +BestsellerCommand -> TextUi: replyToUser("The current best-selling item is {itemName}") + +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Cashier/CashierClassDiagram.puml b/docs/Diagrams/PUML files/Cashier/CashierClassDiagram.puml new file mode 100644 index 0000000000..a177d49e1e --- /dev/null +++ b/docs/Diagrams/PUML files/Cashier/CashierClassDiagram.puml @@ -0,0 +1,68 @@ +@startuml +!include ../Style.puml + +class BestsellerCommand #LightGreen { ++ execute() +} +class TotalProfitCommand #LightGreen { +command: CommandType ++ TotalProfitCommand(command) ++ execute() +} +class ListCommand #LightGreen { +- transactionList: ArrayList ++ execute() +} +class SellCommand #LightGreen { ++execute() +} + +class TextUi #Tan { ++replyToUser(text: String) +} + +class Cashier #MistyRose { + - transactions: ArrayList + + addItem(transaction: Transaction): void + + getTransactions(): ArrayList + + getTransactions(item: Item): ArrayList + + getTransaction(index: int): Transaction + + getTotalRevenue(): float + + getTotalProfit(): float + + getBestseller(): Item +} + +class Transaction #LightSkyBlue { + - dateTime: String + - totalPrice: float + - profit: float + - item: String + - quantity: int + - buyPrice: float + - sellPrice: float + + + Transaction(String, int, float, float) + + Transaction(String, int, float, float, String) +} + +class Item #LightBlue { + + isMark: boolean + + isOOS: boolean + - itemName: String + - quantity: int + - unitOfMeasurement: String + - category: String + - buyPrice: float + - sellPrice: float + +Item(String, int, String, String, float, float) +} + +BestsellerCommand -d->"uses" Cashier +TotalProfitCommand-d->"uses" Cashier +SellCommand-d->"uses" Cashier +ListCommand-d->"uses" Cashier +Cashier -r->TextUi +Cashier --> Transaction : has 1..* +Transaction --> Item : has 1 + +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Cashier/CashierSequenceDiagram.puml b/docs/Diagrams/PUML files/Cashier/CashierSequenceDiagram.puml new file mode 100644 index 0000000000..80f701f224 --- /dev/null +++ b/docs/Diagrams/PUML files/Cashier/CashierSequenceDiagram.puml @@ -0,0 +1,50 @@ +@startuml +!include ../Style.puml +participant ":Command" as Command +participant ":TextUi" as TextUi +participant ":Cashier" as Cashier + +-> Command: execute() +activate Command +Command -> Cashier: addItem(transaction) +activate Cashier +Cashier -> Cashier: transactions.add(transaction) +return +Command -> TextUi: showSellMessage() +activate TextUi +return +alt if itemName is not given +Command -> Cashier: getAllTransactions() +activate Cashier +return transactions +Command -> TextUi : showTransactionsList(); +activate TextUi +return +else if itemName is given +Command -> Cashier: getTransactions(itemName) +activate Cashier +Cashier -> Cashier: Filter transactions +return transactions +Command -> TextUi: showTransactionsList() +activate TextUi +return +end + +Command -> Cashier: getTransaction(index) +activate Cashier +Cashier -> Cashier: transactions.get(index) +return transaction +Command -> TextUi: showTransactionsList() +activate TextUi +return + +alt Revenue + ref over Command, Cashier: getTotalRevenue +else Profit + ref over Command, Cashier: getTotalProfit +end + +ref over Command, Cashier: getBestseller + +return +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Cashier/TotalProfitCommand_SequenceDiagram.puml b/docs/Diagrams/PUML files/Cashier/TotalProfitCommand_SequenceDiagram.puml new file mode 100644 index 0000000000..1e53df17ea --- /dev/null +++ b/docs/Diagrams/PUML files/Cashier/TotalProfitCommand_SequenceDiagram.puml @@ -0,0 +1,26 @@ +@startuml +!include ../Style.puml + +participant ":TotalProfitCommand" as TotalProfitCommand +participant ":Cashier" as Cashier +participant ":TextUi" as TextUi + + +mainframe sd +alt get total revenue + TotalProfitCommand -> Cashier: getTotalRevenue() + activate Cashier + Cashier -> Cashier: Calculate total revenue + Cashier --> TotalProfitCommand: totalRevenue + deactivate Cashier + TotalProfitCommand -> TextUi: replyToUser("You have earned {totalRevenue} in revenue so far.") +else get total profit + TotalProfitCommand -> Cashier: getTotalProfit() + activate Cashier + Cashier -> Cashier: Calculate total profit + Cashier --> TotalProfitCommand: totalProfit + deactivate Cashier + TotalProfitCommand -> TextUi: replyToUser("You have earned {totalProfit} in profits so far.") +end + +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Itemlist/AddCommand_SequenceDiagram.puml b/docs/Diagrams/PUML files/Itemlist/AddCommand_SequenceDiagram.puml new file mode 100644 index 0000000000..bb09b1e541 --- /dev/null +++ b/docs/Diagrams/PUML files/Itemlist/AddCommand_SequenceDiagram.puml @@ -0,0 +1,32 @@ +@startuml +!include ../Style.puml + +actor User +participant ":Parser" as Parser +participant ":AddCommand" as AddCommand +participant ":Itemlist" as Itemlist +participant ":EditCommand" as EditCommand + +User -> Parser: add command +activate Parser + +Parser -> AddCommand: prepareAdd(userInput) +activate AddCommand +activate Itemlist +alt Item exists + AddCommand -> EditCommand: edit Item + activate EditCommand + EditCommand -> Itemlist: editQuantity(index, newQuantity) + deactivate EditCommand + Itemlist --> AddCommand + AddCommand -> User: Output success message +else Item does not exist + AddCommand -> Itemlist: addItem(item) + Itemlist --> AddCommand + +AddCommand -> User: Output success message +deactivate AddCommand +end +deactivate Itemlist +deactivate Parser +@enduml diff --git a/docs/Diagrams/PUML files/Itemlist/EditCommand_ClassDiagram.puml b/docs/Diagrams/PUML files/Itemlist/EditCommand_ClassDiagram.puml new file mode 100644 index 0000000000..4328695f43 --- /dev/null +++ b/docs/Diagrams/PUML files/Itemlist/EditCommand_ClassDiagram.puml @@ -0,0 +1,55 @@ +@startuml +!include ../Style.puml +class EditCommand { + - itemName : String + - newItemName : String + - newQuantity : int + - newUnitOfMeasurement : String + - newCategory : String + - newBuyPrice : float + - newSellPrice : float + + EditCommand(...) + + execute() : void +} + +class Item { + - itemName : String + - quantity : int + - unitOfMeasurement : String + - category : String + - buyPrice : float + - sellPrice : float +} + +class Itemlist { + + getItems() : List + + getItem(index: int) : Item' +} + +class Promotion { + - itemName : String + + setItemName(newItemName: String) : void +} + +class Promotionlist { + + itemIsOnPromo(itemName: String) : boolean + + getPromotion(itemName: String) : Promotion + + getAllPromotion() : List +} + +class Storage { + + overwriteFile(items: List) : void +} + +class PromotionStorage { + + overwritePromotionFile(promotions: List) : void +} + +EditCommand -> Item +EditCommand --> Itemlist +EditCommand --> Promotion : uses > +EditCommand --> Promotionlist +Storage <-- EditCommand +PromotionStorage <-- EditCommand + +@enduml diff --git a/docs/Diagrams/PUML files/Itemlist/EditCommand_SequenceDiagram.puml b/docs/Diagrams/PUML files/Itemlist/EditCommand_SequenceDiagram.puml new file mode 100644 index 0000000000..3c32aa8b3b --- /dev/null +++ b/docs/Diagrams/PUML files/Itemlist/EditCommand_SequenceDiagram.puml @@ -0,0 +1,115 @@ +@startuml +!include ../Style.puml +actor User +participant "editCommand :EditCommand" as EditCommand +participant "ui :TextUi" as UI +participant "itemList :Itemlist" as Itemlist +participant "item :Item" as Item +participant "storage :Storage" as Storage + +User -> EditCommand : execute() +activate EditCommand + +EditCommand -> Itemlist : getItems() +activate Itemlist +Itemlist --> EditCommand : return items +deactivate Itemlist + +EditCommand -> EditCommand : find item index +activate EditCommand +EditCommand --> EditCommand : index found / -1 if not found +deactivate EditCommand + +alt item not found + EditCommand -> UI : replyToUser("item not found!") + activate UI + UI --> EditCommand + deactivate UI +else item found + EditCommand -> Itemlist : getItem(index) + activate Itemlist + Itemlist --> EditCommand : return item + deactivate Itemlist + + loop For each editable attribute + alt newItemName exists + EditCommand -> UI : showEditMessage(newItemName) + activate UI + UI --> EditCommand + deactivate UI + EditCommand -> Item : setItemName(newItemName) + activate Item + Item --> EditCommand + deactivate Item + end + alt newQuantity exists + EditCommand -> UI : showEditMessage(newQuantity) + activate UI + UI --> EditCommand + deactivate UI + EditCommand -> Item : setQuantity(newQuantity) + activate Item + Item --> EditCommand + deactivate Item + end + alt newUnitOfMeasurement exists + EditCommand -> UI : showEditMessage(newUnitOfMeasurement) + activate UI + UI --> EditCommand + deactivate UI + EditCommand -> Item : setUnitOfMeasurement(newUnitOfMeasurement) + activate Item + Item --> EditCommand + deactivate Item + end + alt newCategory exists + EditCommand -> UI : showEditMessage(newCategory) + activate UI + UI --> EditCommand + deactivate UI + EditCommand -> Item : setCategory(newCategory) + activate Item + Item --> EditCommand + deactivate Item + end + alt newBuyPrice exists + EditCommand -> UI : showEditMessage(newBuyPrice) + activate UI + UI --> EditCommand + deactivate UI + EditCommand -> Item : setBuyPrice(newBuyPrice) + activate Item + Item --> EditCommand + deactivate Item + end + alt newSellPrice exists + EditCommand -> UI : showEditMessage(newSellPrice) + activate UI + UI --> EditCommand + deactivate UI + EditCommand -> Item : setSellPrice(newSellPrice) + activate Item + Item --> EditCommand + deactivate Item + end + + end loop + + EditCommand -> UI : replyToUser("End of Edits") + activate UI + UI --> EditCommand + deactivate UI + + EditCommand -> Storage : overwriteFile(Itemlist.getItems()) + activate Storage + Storage --> EditCommand + deactivate Storage +end + +EditCommand -> UI : replyToUser("") +activate UI +UI --> EditCommand +deactivate UI +hide footbox +deactivate EditCommand +@enduml diff --git a/docs/Diagrams/PUML files/Itemlist/ListItems_SequenceDiagram.puml b/docs/Diagrams/PUML files/Itemlist/ListItems_SequenceDiagram.puml new file mode 100644 index 0000000000..d0a95f1e81 --- /dev/null +++ b/docs/Diagrams/PUML files/Itemlist/ListItems_SequenceDiagram.puml @@ -0,0 +1,40 @@ +@startuml +!include ../Style.puml +actor User +participant "ListCommand :ListCommand" as LC +participant "UI :TextUi" as UI +participant "ItemList :Itemlist" as IL + +User -> LC : execute() +activate LC + +LC -> IL : isEmpty() +activate IL +IL --> LC : false +deactivate IL + +alt category equals "NA" and not isListMarked + LC -> UI : showList(itemList) + activate UI + UI --> LC + deactivate UI +else category not equals "NA" and not isListMarked + LC -> UI : showCustomizedList(itemList, category, false) + activate UI + UI --> LC + deactivate UI +else category equals "NA" and isListMarked + LC -> UI : showCustomizedList(itemList, "NA", true) + activate UI + UI --> LC + deactivate UI +else + LC -> UI : showCustomizedList(itemList, category, true) + activate UI + UI --> LC + deactivate UI + deactivate LC +end + +hide footbox +@enduml diff --git a/docs/Diagrams/PUML files/Parser/Parser_ClassDiagram.PUML b/docs/Diagrams/PUML files/Parser/Parser_ClassDiagram.PUML new file mode 100644 index 0000000000..45c68b2944 --- /dev/null +++ b/docs/Diagrams/PUML files/Parser/Parser_ClassDiagram.PUML @@ -0,0 +1,65 @@ +@startuml +!include ../Style.puml +package "parser" { + class Parser { + -logger: Logger + +parseInput(String): Command + +prepareHelp(String): Command + +prepareAdd(String): Command + +prepareDelete(String): Command + +prepareSell(String): Command + +prepareFind(String): Command + +prepareEdit(String): Command + +preparePromotion(String): Command + +prepareDeletePromo(String): Command + +prepareItemList(String): Command + +preparePromotionList(): Command + +prepareMark(String): Command + +prepareUnmark(String): Command + +prepareTransactionList(String): Command + +prepareLowStock(String): Command + } +} + +package "command" { + class Command +} + +package "common" { + class HelpMessages + class Messages +} + +package "exceptions" { + class CommandFormatException + class EditException + class InvalidDateException +} + +package "itemlist" { + class Cashier + class Itemlist +} + +package "promotion" { + enum Month + class Promotionlist +} + +Parser --> Command +Parser --> Logger +Parser --> HelpMessages +Parser --> Messages +Parser --> CommandFormatException +Parser --> EditException +Parser --> InvalidDateException +Parser --> Cashier +Parser --> Itemlist +Parser --> Month +Parser --> Promotionlist + +Parser *-- Command + +EditException --> CommandFormatException + +@enduml diff --git a/docs/Diagrams/PUML files/Promotion/AddPromotion.puml b/docs/Diagrams/PUML files/Promotion/AddPromotion.puml new file mode 100644 index 0000000000..b7d63aa60b --- /dev/null +++ b/docs/Diagrams/PUML files/Promotion/AddPromotion.puml @@ -0,0 +1,34 @@ +@startuml +!include ../Style.puml +'https://plantuml.com/sequence-diagram + +mainframe sd AddPromotion +autoactivate on + +participant ":PromotionCommand" as PromotionCommand +participant ":Promotionlist" as Promotionlist +participant ":TextUi" as TextUi +participant ":Itemlist" as Itemlist +participant ":Promotion" as Promotion + +PromotionCommand -> Promotionlist : addPromotion(promotion) +Promotionlist -> Itemlist : isItemExist(itemName) +Itemlist --> Promotionlist : true +Promotionlist -> Promotionlist : itemIsOnPromo(itemName) +return false +Promotionlist -> Promotionlist : isValidDiscount() +return true +Promotionlist -> Promotionlist : isValidMonth() +return true +Promotionlist -> Promotionlist : isValidTime() +return true +Promotionlist -> Promotionlist : isValidDuration() +return true +Promotionlist -> Promotion : add(promotion) +Promotion --> Promotionlist +Promotionlist -> TextUi : print(AddPromotion) +TextUi --> Promotionlist +Promotionlist --> PromotionCommand + +hide footbox +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Promotion/AddPromotion_ClassDiagram.puml b/docs/Diagrams/PUML files/Promotion/AddPromotion_ClassDiagram.puml new file mode 100644 index 0000000000..b1f71ab6f5 --- /dev/null +++ b/docs/Diagrams/PUML files/Promotion/AddPromotion_ClassDiagram.puml @@ -0,0 +1,70 @@ +@startuml +!include ../Style.puml +'https://plantuml.com/sequence-diagram + +skinparam classFontColor automatic + +class AddPromotionCommand #LightPink { + # promotion : Promotion + + execute() +} + +class Promotionlist #Honeydew { + - promotions : ArrayList + + getIndex(promotion : Promotions) + + itemIsOnPromo(itemName: String) + + isLeapYear(year: Int) + + addPromotion(promotion : Promotions) + + isValidDiscount(discount: Float) + + isValidTime(startTime: Int, endTime: Int) + + isValidDuration(...) + + isValidMonth(date: Int, month: Month, year: Int) + + getAllPromotion() +} + +note right + Parameters for isValidDuration + startDate : Int, + startMonth : Month, + startYear : Int, + endDate : Int, + endMonth : Month, + endYear : Int +end note + + +class Promotion #LightBlue { + # itemName : String + # discount : Float + # startDate : Int + # startMonth : Month + # startYear : Int + # endDate : Int + # endMonth : Month + # endYear : Int + # startTime : Int + # endTime : Int +} + +enum Month #LightYellow { + JAN(1) + FEB(2) + MAR(3) + APR(4) + MAY(5) + JUN(6) + JUL(7) + AUG(8) + SEP(9) + OCT(10) + NOV(11) + DEC(12) +} + +AddPromotionCommand --> Promotionlist : uses +Promotionlist --> Promotion : 0..* + + +hide circle +skinparam classAttributeIconSize 0 +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Promotion/DeletePromotion.puml b/docs/Diagrams/PUML files/Promotion/DeletePromotion.puml new file mode 100644 index 0000000000..ad0b27561e --- /dev/null +++ b/docs/Diagrams/PUML files/Promotion/DeletePromotion.puml @@ -0,0 +1,23 @@ +@startuml +!include ../Style.puml +'https://plantuml.com/sequence-diagram + +mainframe sd DeletePromotion + +participant ":PromotionCommand" as PromotionCommand +participant ":Promotionlist" as Promotionlist +participant ":TextUi" as TextUi + +alt item has an ongoing promotion + PromotionCommand -> Promotionlist : getPromotion(itemName) + Promotionlist --> PromotionCommand : promotion + PromotionCommand -> Promotionlist : getIndex(promotion) + Promotionlist --> PromotionCommand : index + PromotionCommand -> Promotionlist : deletePromotion(index) + PromotionCommand -> TextUi : print(deletePromotion) +else item does not have any promotion + PromotionCommand -> TextUi : print(ITEM_NOT_ON_PROMO) +end + +hide footbox +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Promotion/Promotion_SequenceDigram.puml b/docs/Diagrams/PUML files/Promotion/Promotion_SequenceDigram.puml new file mode 100644 index 0000000000..cbe36a4108 --- /dev/null +++ b/docs/Diagrams/PUML files/Promotion/Promotion_SequenceDigram.puml @@ -0,0 +1,26 @@ +@startuml +!include ../Style.puml +'https://plantuml.com/sequence-diagram + +participant ":PromotionCommand" as PromotionCommand +participant ":Promotionlist" as PromotionList +participant ":TextUi" as TextUi +participant ":Itemlist" as Itemlist +participant ":Promotion" as Promotion + + +-> PromotionCommand: execute() + +alt if user wants to create a promotion + ref over PromotionCommand, PromotionList, Itemlist, Promotion, TextUi : AddPromotion +else if user wants to delete a promotion + ref over PromotionCommand, PromotionList, TextUi : DeletePromotion +else if user wants to list all promotions + PromotionCommand -> TextUi: printPromotion() +else if user enters invalid command +end + +<-- PromotionCommand + +hide footbox +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Storage/Storage_ClassDiagram.puml b/docs/Diagrams/PUML files/Storage/Storage_ClassDiagram.puml new file mode 100644 index 0000000000..e61470ee65 --- /dev/null +++ b/docs/Diagrams/PUML files/Storage/Storage_ClassDiagram.puml @@ -0,0 +1,58 @@ +@startuml +!include ../Style.puml + +class Storage { + +readFromFile(fileName: String) + +addToFile(items: ArrayList) + +overwriteFile(items: ArrayList) +} + +class TransactionLogs { + +readFromFile(fileName: String) + +addToLog(transactions: ArrayList) +} + +class PromotionStorage { + +readFromFile(fileName: String) + +overwritePromotionFile(promotions: ArrayList) +} + +class Itemlist { + +addItem(item: Item) +} + +class Promotionlist { + +addPromotion(promotion: Promotion) +} + +class AddCommand { + +execute() +} + +class EditCommand { + +execute() +} + +class SellCommand { + +execute() +} + +class AddPromotionCommand { + +execute() +} + +class DeletePromotionCommand { + +execute() +} + +Storage <|-- TransactionLogs +Storage <|-- PromotionStorage + +Storage --> Itemlist: uses +PromotionStorage --> Promotionlist: uses +AddCommand --> Storage: uses +EditCommand --> Storage: uses +SellCommand --> TransactionLogs: uses +DeletePromotionCommand --> PromotionStorage: uses +AddPromotionCommand --> PromotionStorage: uses +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Storage/Storage_sequenceDiagram.puml b/docs/Diagrams/PUML files/Storage/Storage_sequenceDiagram.puml new file mode 100644 index 0000000000..4e46708d64 --- /dev/null +++ b/docs/Diagrams/PUML files/Storage/Storage_sequenceDiagram.puml @@ -0,0 +1,40 @@ +@startuml +!include ../Style.puml +participant ":Client" +participant ":Storage" +participant ":Itemlist" +participant ":Item" +participant ":Scanner" +participant ":File" + +":Client" -> ":Storage": readFromFile(fileName) +activate ":Storage" +":Storage" -> ":File": new File(fileName) +activate ":File" +":Storage" -> ":Scanner": new Scanner(File) +activate ":Scanner" +":Scanner" -> ":File": interpretLines(scanner) +":File" --> ":Storage": Close Scanner +deactivate ":Scanner" +deactivate ":File" +":Storage" --> ":Client" +deactivate ":Storage" + +":Client" -> ":Storage": addToFile(items) +activate ":Storage" +":Storage" -> ":Itemlist": addItem(toAdd) +activate ":Itemlist" +":Itemlist" --> ":Storage" +deactivate ":Itemlist" +":Storage" -> ":Storage": updateFile(descriptionAdded, true) +":Storage" --> ":Client" +deactivate ":Storage" + +":Client" -> ":Storage": overwriteFile(items) +activate ":Storage" +loop for each item in items + ":Storage" -> ":Storage": updateFile(descriptionAdded, ifAppend) +end +":Storage" --> ":Client" +deactivate ":Storage" +@enduml \ No newline at end of file diff --git a/docs/Diagrams/PUML files/Style.puml b/docs/Diagrams/PUML files/Style.puml new file mode 100644 index 0000000000..c9c000600f --- /dev/null +++ b/docs/Diagrams/PUML files/Style.puml @@ -0,0 +1,39 @@ +!define USER_COLOR #000000 + +skinparam Package { + BackgroundColor #FFFFFF + BorderThickness 1 + FontSize 16 +} + +skinparam Class { + FontColor #000000 + FontSize 15 + BorderThickness 1 + BorderColor #000000 + StereotypeFontColor #FFFFFF + FontName Arial +} + +skinparam Sequence { + MessageAlign center + BoxFontSize 15 + BoxPadding 0 + BoxFontColor #FFFFFF + FontName Arial +} + +skinparam Participant { + FontColor #000000 + Padding 20 +} + +skinparam MinClassWidth 50 +skinparam ParticipantPadding 10 +skinparam Shadowing false +skinparam DefaultTextAlignment center +skinparam packageStyle Rectangle +skinparam classAttributeIconSize 0 + +hide footbox +hide circle \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..8cd7858a66 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# Stock Master -{Give product intro here} +StockMaster helps small business owners manage inventory of their business. It is optimized for CLI users so that frequent tasks can be done faster by typing in commands. Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..b35658b245 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,41 +2,281 @@ ## Introduction -{Give a product intro} +StockMaster is a desktop inventory and management that provides the user with a range of tools and features to help them +manage and operate their business. It is targeted at those who can type fast and prefer Command Line Interface (CLI) application. ## Quick Start -{Give steps to get started quickly} - 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +2. Download the latest version of `StockMaster` from [here](https://github.com/AY2324S2-CS2113-T15-4/tp/releases/tag/v2.1b). +3. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar CS2113-T15-4.StockMaster.jar` command to run the application. -## Features +## Features -{Give detailed description of each feature} +### Adding an item: `add` +Adds a new item to the list of items. -### Adding a todo: `todo` -Adds a new item to the list of todo items. +Format: `add ITEM_NAME qty/ITEM_QUANTITY /UNIT_OF_MEASUREMENT [cat/CATEGORY] buy/BUY_PRICE sell/SELL_PRICE` -Format: `todo n/TODO_NAME d/DEADLINE` +* `CATEGORY` is an optional field. If blank, it will default to `N/A`. -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +> [ !NOTE ] +> +> Addition of an item with the same `ITEM_NAME` will increase the `ITEM_QUANTITY`, and update the `UNIT_OF_MEASUREMENT`, +> `CATEGORY`, `BUY_PRICE` and `SELL_PRICE` instead of adding a duplicated entry for the same item. Example of usage: +``` +add apple qty/50 /pieces cat/fruits buy/4 sell/5 +add phone qty/5 /pieces cat/Electronics buy/100 sell/500 +``` + +### Listing items: `list_items` +Lists stored items according to what user requests. + +Format: `list_items [marked] [cat/CATEGORY]` + +* By default, `list_items` on its own will list all stored items. +* `marked` is an optional field. If used, it will only list marked items. +* `CATEGORY` is an optional field. If used, it will only list items of that category. + +Example of usage: +``` +list_items +list_items cat/Electronics +list_items marked cat/fruits +``` +Example output: +``` +1. [ ] apple (Qty: 50 pieces, Buy: $4.0, Sell: $5.0, Category: fruits) +``` +_**Note**_
+- _marked **MUST** be before cat/[CATEGORY]_ +- The `[ ]` before each item represents if an item is marked or not. More info on mark feature can be found below. + +### Deleting an item: `del` +Deletes the item from the list of items. + +Format: `del ITEM_NAME` + +Example of usage: +``` +del apples +``` + +### Selling an item: `sell` +Sells a quantity of an item from the list of items at a stated price. + +Format: `sell ITEM_NAME qty/SELL_QUANTITY` + + +Example of usage: +``` +sell apple qty/20 +``` + +### Marking an item: `mark` +Marks items in the inventory list. + +Format: `mark ITEM_NAME` + +Example of usage: +``` +mark apple iphone +``` + +### Unmarking an item: `unmark` +Unmarks a marked item in the inventory list. + +Format: `unmark ITEM_NAME` + +Example of usage: +``` +unmark apple iphone +``` + +### Editing an item: `edit` +Edits the parameters of the item. + +Format: `edit ITEM_NAME [name/NEW_NAME] [qty/NEW_QUANTITY] [uom/NEW_UOM] +[cat/NEW_CATEGORY] [buy/NEW_BUY_PRICE] [sell/NEW_SELL_PRICE]`
+ +Example of usage: +``` +edit apple name/green apple qty/10 uom/pieces cat/fruit buy/1.00 sell/2.00 +edit fish name/Salmon qty/1 uom/pieces cat/fish device buy/1.00 sell/10.00 +``` +User can choose to edit at least 1 parameter up to all available parameters. + +Example of usage: +``` +edit apple qty/45 buy/3.50 sell/5.50 +edit fish name/Salmon qty/1 cat/fish +``` + +Example Output: +``` +Edited: +Quantity of apple from 50 to 45 +Buy Price of apple from 4.0 to 3.5 +Sell Price of apple from 5.0 to 5.5 +End of Edits +``` + +### Finding an item: `find` +Finds all items that contains `KEYWORD` + +Format: `find [/FILTER1/FILTER2] KEYWORD` + +* Filter are optional and will specify the parameters that is being searched. +* Filters can be `item`, `qty`, `cat`, `uom`, `buy`, `sell`. Any other values will show an empty list. +* If no filter is applied, it will search all items that contains `KEYWORD. +* The search is case-insensitive e.g. `apple` will match `Apple`. +* Partial words will be matched e.g. `app` will match `Apple`. + +Example of usage: +``` +find /qty/cat Apple // search for `Apple` under `ITEM_QUANTITY` and `CATEGORY` +find Apple // search all items that contains `Apple` +``` + + -`todo n/Write the rest of the User Guide d/next week` +### Get bestselling item: `bestseller`. +Reads all the Transactions and returns the item with the highest profit. -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +Format: `bestseller` + +Example of usage: +``` +bestseller +``` + +Expected output: +``` +The current best-selling item is {ITEM_NAME}. +``` + +### Get total profits: `total_profit` +Reads all the Transactions and returns the total profits. + +Format: `total_profit` + +Example of usage: +``` +total_profit +``` + +Expected output: +``` +You have earned {PROFIT} in profits so far. +``` + +### Get total revenue: `total_revenue` +Reads all the Transactions and returns the total profits. + +Format: `total_revenue` + +Example of usage: +``` +total_revenue +``` + +Expected output: +``` +You have earned {REVENUE} in revenue so far. +``` + +### Add promotion to items: `promotion` +Creates a promotion for items that changes the sell price. + +Format: `promotion ITEM_NAME discount/DISCOUNT period /from DAY MONTH YEAR /to DAY MONTH YEAR +time /from START_TIME /to END_TIME` + +* `DISCOUNT` ranges from 0 to 100 and can take in up to 2 decimal place. +* Format for `MONTH` is 3 alphabets `MMM`. E.g. For January, `Jan`. For December, `Dec`. +* `DAY` must be a valid for the specific `[MONTH]` E.g. `30 Feb 2024` is not allowed. +* `START_TIME` & `END_TIME` must range from 0000 to 2359. In addition, time range must be valid. + +Example of usage: +``` +promotion apple discount/50 period /from 2 Apr 2024 /to 4 Apr 2025 time /from 0000 /to 2359 +``` +### Delete a promotion: `del_promo` +Deletes a promotion for an item. + +Format: `del_promo ITEM_NAME` + +Example of usage: +``` +del_promo apple +``` +### List promotions: `list_promotions` +List all the promotions created. + +Format: `list_promotions` + +Example of usage: +``` +list_promotions +``` + +### Low Stock Reminder: `low_stock` +lists all out-of-stock items and low-in-stock items.
+An item is considered low-in-stock if its quantity is below the stated threshold amount. + +Format: `low_stock /AMOUNT` + +Example of usage: +``` +low_stock /15 +``` + +### List all available commands: `help` +Lists all commands as per the command summary below. + +Format: `help [c/COMMAND]` + +* `COMMAND` is optional. Specifying the command will give a more comprehensive + description of the command. + +Example of usage: +``` +help +help c/add +``` + + +### Closing the app: `exit` +Saves and closes the app safely. + +Format: `exit` ## FAQ **Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: Simply copy and paste the saved folders that is created upon launch of +the application. ## Command Summary -{Give a 'cheat sheet' of commands here} - -* Add todo `todo n/TODO_NAME d/DEADLINE` +| Action | Format, Examples | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Add item | `add ITEM_NAME qty/ITEM_QUANTITY /UNIT_OF_MEASUREMENT [cat/CATEGORY] buy/BUY_PRICE sell/SELL_PRICE`
`e.g. add apple qty/50 /pieces cat/fruits buy/4.50 sell/5` | +| Delete item | `del ITEM_NAME`
`e.g. del Apple` | +| Edit item | `edit ITEM_NAME [name/NEW_NAME] [qty/NEW_QUANTITY] [uom/NEW_UOM] [cat/NEW_CATEGORY] [buy/NEW_BUY_PRICE] [sell/NEW_SELL_PRICE]`
`e.g. edit apple name/green apple qty/10 uom/pieces cat/fruit buy/1.00 sell/2.00` | +| Find item | `find [/FILTER] KEYWORD`
`e.g. find University` | +| Sell item | `sell ITEM_NAME qty/SELL_QUANTITY `
`e.g. sell apple qty/50` | +| Mark item | `mark ITEM_NAME` | +| Unmark item | `unmark ITEM_NAME` | +| List Inventory | `list_items [marked] [cat/CATEGORY]` | +| Get Best Seller | `bestseller` | +| Get Profit | `total_profit` | +| Get Revenue | `total_revenue` | +| Create Promotion | `promotion ITEM_NAME discount/DISCOUNT period /from DATE MONTH YEAR /to DATE MONTH YEAR time /from TIME /to TIME`
`e.g. promotion apple discount/50 period /from 2 Apr 2024 /to 4 Apr 2024 time /from 1200 /to 1500` | +| Delete Promotion | `del_promo ITEM_NAME`
`e.g. del_promo apple` | +| List Promotion | `list_promotions` | +| Low Stock Reminder | `low_stock /AMOUNT` | +| Help | `help [c/COMMAND]` | +| Exit | `exit` | diff --git a/docs/team/fureimi.md b/docs/team/fureimi.md new file mode 100644 index 0000000000..81a867f9a5 --- /dev/null +++ b/docs/team/fureimi.md @@ -0,0 +1,44 @@ +# Min Guanlin - Project Portfolio Page + +## Project: StockMaster + +StockMaster is a desktop inventory and management that provides the user with a range of tools and features to help them +manage and operate their business. + +### Code Contribution +- **New Feature**: Added the ability to: + - List all items, + - List all items of a certain category, + - List all marked items, + - List all marked items of a certain category. + - **What it does**: Allows user to list items in the item list according to the user needs. + - **Justification**: This feature is crucial for StockMaster, as the main purpose of our application is for store owners +to keep track of their inventory. It also provides a convenient way for users to check a item of a certain category as +they may have many items in the whole item list, making it hard to search for specific items. In addition, users might +only want to see their marked items. The mark feature is explained below. + +- **New Feature**: Added the ability to mark and unmark specific items in the item list. + - **What it does**: Allows users to mark specific item in the item list. The user can also unmark a marked item. + - **Justification**: Users might want to mark items for many reasons, such as to keep track of their sales as they + may be a high in demand item. These items might also be from different categories, so user cannot list them by + category. This feature hence provides a convenient way for users to access items they want to keep an eye on. + +- **New Feature**: Added the ability to edit current item parameters. + - **What it does**: Allows users to edit specific parameters of existing items, such as quantity, category, name, etc. + - **Justification**: This feature is crucial to our application as it allows users to rectify user errors when + adding items to the item list. In addition, it allows the user to change the items to reflect accurately the status + of each item. For example, the user may want to change the selling price of specific items due to a anticipation of a + surge in demand. + - **Highlights**: The implementation was challenging as it required integration with `Itemlist` as well as `item` to + ensure that the parameters are edited accurately. In addition, this enhancement allows users to edit multiple + parameters at once, which increases convenience for the user. + +- **Code Contributed** : [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=fureimi&breakdown=true) + +- **Documentation**: + - User Guide: + - Added documentation for the features `list_items`, `mark`, `unmark` and `edit` + - Update the command summary. + - Developer Guide: + - Added class diagram of the `EditCommand` class. + - Added implementation details and sequence diagram of both the `list_items` and `edit` features. diff --git a/docs/team/fxe025.md b/docs/team/fxe025.md new file mode 100644 index 0000000000..b1914c2488 --- /dev/null +++ b/docs/team/fxe025.md @@ -0,0 +1,37 @@ +# Fu Xueer - Project Portfolio Page + + +## Project: StockMaster +StockMaster is a desktop inventory and management that provides the user with a range of tools and features to help them +manage and operate their business. + + +### Code Contribution + +- **New Feature:** Add Storage feature + - What it does: Write information from an itemlist to file. Read information from the file when program starts + - Justification: This feature is important in saving stock information and any update to stock, during the execution of +StockMaster. When the user restarts the program, information from previous runs can be automatically retrieved. User can also +use the text file created by this feature for reference. +- **New Feature:** Add Transaction feature + - What it does: Store date and time, selling price, cost, quantity and profit made of a type of item that's already sold + - Justification: Transactions contain additional information of items in stock when they are being sold, and those are not necessary +to be stored in itemlist. The transaction feature is useful in saving these information. +- **New Feature:** Add Cashier feature + - What it does: A list to store past transactions + - Justification: This is a necessary feature to store and edit transactions list by user command +- **New Feature:** Add TransactionLogs feature + - What it does: Write information from a transactionlist to file. Read information from the file when program starts + - Justification: Transactions are stored in different format than stock, and they contain additional information like profit and date. + Storing transactions in another file helps the user to obtain required information more easily. + +**Code Contributed:** [RepoSense link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=fxe025&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=isaaceng7&tabRepo=AY2324S2-CS2113-T15-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +- **Documentation** + - User Guide: + - Update the introduction of user guide + - Update feature guide for edit command feature + - Developer Guide: + - Add Storage feature + - Class diagram + - Sequence diagram diff --git a/docs/team/hengshuhong.md b/docs/team/hengshuhong.md new file mode 100644 index 0000000000..26a9dfa88f --- /dev/null +++ b/docs/team/hengshuhong.md @@ -0,0 +1,56 @@ +# Heng Shu Hong's Project Portfolio Page + + +## Project: StockMaster + +StockMaster is a desktop inventory and management that provides the user with a range of tools and features to help them +manage and operate their business. + + +### Code Contribution + +- **New Feature: Added the capability to add/delete/list promotions** + - What it does: allows the user to add a Promotion for a certain ITEM by entering the discount, startDate, + endDate, startTime and endTime. When a Promotion is added for an ITEM, and the store owner uses the sell + feature in the application, the discount will be immediately applied to the sell price and the discounted price will + be shown if it is within the promotion period. + - Justification: This feature improves the application as the user now have the option to create a discount campaign + and make adjustments to the sell price automatically rather than having to manually use the edit feature. + - Highlights: This enhancement was challenging as it involved the integration with the sell command. In particular, + one difficulty was to ensure that the original price of the ITEM would not be changed even during a promotion period. + Another difficulty is to check if the item is within the promotion period for the given date and time. + + +- **New Feature: Enhanced the find command to allow the user to search based on the specified item information** + - What it does: allows the user to look through a filtered list to find the ITEM information based on the KEYWORD + For instance, the user can find all ITEMS that has a buy price or sell price of $`1`. + - Justification: This feature increases the capabilities of the search function, as it can allow the user to quickly + retrieve the data that is being searched for by narrow the scope of the search. + - Highlights: The difficulty in this enhancement was the approach to implement this improvement. This is because this + enhancement is an extension of the previous find command, meaning that it must still be able to retain its original + feature, but yet still able to provide more than 1 filter to narrow the scope. + + +- **New Feature: Enhanced the add command to prevent duplicate entries** + - What it does: It prevents the user from accidentally inputting duplicated items. Instead, when the user adds a + duplicated item, the item information will be updated based on the latest input and the quantity will be increased. + - Justification: This feature enables the user to not have duplicate entries of the same items to avoid the + overflowing of the inventory list with duplicated item. + +- **Code Contributed:** [RepoSense link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=hengshuhong&breakdown=true) + +- **Documentation** + - User Guide: + - Add documentation for the features `add`, `find`, `promotion`, `del_promo` and `list_promotions` [#86](https://github.com/AY2324S2-CS2113-T15-4/tp/pull/86) + - Add the Quick Start + - Update the Command Summary + + - Developer Guide: + - Add implementation details of the `promotion` feature. [#176](https://github.com/AY2324S2-CS2113-T15-4/tp/pull/176) + - Update Product Scope [#181](https://github.com/AY2324S2-CS2113-T15-4/tp/pull/181/files) + - Add non-functional requirements. [#181](https://github.com/AY2324S2-CS2113-T15-4/tp/pull/181/files) + +- **Review Contributions** + - Fix bug for team members that caused the application to crash. [#184](https://github.com/AY2324S2-CS2113-T15-4/tp/pull/184) + - Fix test case that failed [#187](https://github.com/AY2324S2-CS2113-T15-4/tp/pull/187) + - Review team's UML Diagrams \ No newline at end of file diff --git a/docs/team/joellimjr.md b/docs/team/joellimjr.md new file mode 100644 index 0000000000..afb669b2fb --- /dev/null +++ b/docs/team/joellimjr.md @@ -0,0 +1,69 @@ +# Lim Jing Rong, Joel's Project Portfolio Page + + +## Project: StockMaster + +StockMaster is a desktop inventory and management that provides the user with a range of tools and features to help them +manage and operate their business. + + +### Code Contribution + +- **New Feature:** Added Parser feature + - What it does: Parses user inputs, and extracts the command word. Then, checks + if the input matches the required format for the relevant command. It then + trims the inputs, converts strings to lowercase, and ensures numbers are of an + appropriate value, throwing exceptions if it fails. + - Justification: This feature centralises the processing of the user input and + filters any erroneous inputs. This allows the commands themselves to be more + streamlined as the parameters required would be prepared and checked in advance. + - Highlights: This feature was challenging as it had to be updated constantly throughout + the StockMaster's development, while working closely with other team members to + understand the requirements of their features, especially regarding exception handling. + + +- **New Feature:** Added the ability to sell items + - What it does: Sells a stated quantity of an existing item and calculates the total amount + of money earned. It then updates the remaining quantity of the item in stock. + - Justification: This feature allows users to easily update the stock of an item when it is + sold. It also enables the cashier and transaction classes to keep track + of when and what items were sold, and how much they were sold for. + + +- **New Feature:** Added the low-stock reminder + - What it does: It lists all items that are out of stock and low in stock. The threshold + for what is considered low in stock can be set by the user when calling the low stock + command. The reminder is also run automatically during StockMaster's start-up process + with a default low stock threshold of 10. + - Justification: It reminds the user upon starting up the app to restock out-of-stock items + and warn the user about the items that are running low in stock. + + +- **New Feature:** Detailed help command + - What it does: An extension of the help command, it gives users more detailed information about + a specific command along with examples. + - Justification: Some of the commands have many input variables, some of which are optional. + This feature aims to help users understand the specific requirements of the commands + + +- **Code Contributed:** [RepoSense link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=Joellimjr&tabRepo=AY2324S2-CS2113-T15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +- **Documentation** + - User Guide: + - Added documentation for the features `del`,`sell` and `low_stock` + - Update the command summary + - Developer Guide: + - Added implementation details and class diagram of the `Parser` class. + - Added implementation details and sequence diagram of the `add` feature. + + +- **J-Unit Testing** + - Added J-Unit testing: + - ParserTest + - AddCommandTest + - EditCommandTest + - HelpCommandTest + - SellCommandTest + - LowStockCommandTest + \ No newline at end of file diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/lowtl.md b/docs/team/lowtl.md new file mode 100644 index 0000000000..26ade01781 --- /dev/null +++ b/docs/team/lowtl.md @@ -0,0 +1,33 @@ +# Low Tjun Lym - Project Portfolio Page + +## Project: StockMaster +StockMaster is a desktop inventory and management that provides the user with a range of tools and features to help them +manage and operate their business. + +**Code Contributed:** +[Reposense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=LowTL&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=LowTL&tabRepo=AY2324S2-CS2113-T15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Enhancements implemented +* `Cashier` class. + * Allows users to create and store `Transactions`, in an extension of the `item` class. + * Contains the `getTotalProfit`, `getTotalRevenue`, and `getBestseller` methods, for basic business analysis. +* Logging for all necessary classes +* Testing for `ListCommand`, `BestsellerCommand`, `TotalProfitCommand` and others. + +### Contributions to UG +* Feature guide for functions related to `Cashier` +* Initial version + +### Contributions to DG +* Architecture Diagram of overall product +* `Cashier` class + * Implementation + * Class and sequence diagram + +### Other contributions +* Reviewing of teammates' code outside of Pull Requests +* Troubleshooting, debugging and catching exceptions. +* Assigning issues to relevant team members +* Organising and Formatting UG and DG + + diff --git a/src/main/java/command/AddCommand.java b/src/main/java/command/AddCommand.java new file mode 100644 index 0000000000..0a5251bc16 --- /dev/null +++ b/src/main/java/command/AddCommand.java @@ -0,0 +1,104 @@ +//@@author HengShuHong +package command; + +import item.Item; +import itemlist.Itemlist; +import storage.Storage; + +public class AddCommand extends Command { + + public static final String MESSAGE_SUCCESS = "added: "; + protected String itemName; + protected int quantity; + protected String unitOfMeasurement; + protected String category; + protected float buyPrice; + protected float sellPrice; + private final Item toAdd; + + public AddCommand(String itemName, int quantity, String unitOfMeasurement, String category, float buyPrice, + float sellPrice) { + this.itemName = itemName.toLowerCase(); + this.quantity = quantity; + this.unitOfMeasurement = unitOfMeasurement; + this.category = category; + this.buyPrice = buyPrice; + this.sellPrice = sellPrice; + this.toAdd = new Item(itemName, quantity, unitOfMeasurement, category, buyPrice, sellPrice); + LOGGER.info("AddCommand successfully created."); + } + + public String getItemName() { + return itemName; + } + public int getQuantity() { + return quantity; + } + public String getCategory() { + return category; + } + public String getUnitOfMeasurement() { + return unitOfMeasurement; + } + public float getBuyPrice() { + return buyPrice; + } + public float getSellPrice() { + return sellPrice; + } + + + /** + * Adds item to the item list + * Category is an optional parameter and will be set to "NA" if left empty + */ + @Override + public void execute() { + if (Itemlist.itemIsExist(itemName)) { + updateItemInfo(itemName); + LOGGER.info("Edited item instead."); + } else { + Itemlist.addItem(toAdd); + LOGGER.info("Item added successfully."); + System.out.print(MESSAGE_SUCCESS + getItemName() + " (Qty: " + getQuantity() + " " + getUnitOfMeasurement() + + ", Buy: $" + String.format("%.2f", getBuyPrice()) + ", Sell: $" + + String.format("%.2f", getSellPrice()) + ")"); + Storage.addToFile(Itemlist.getItems()); + if (!category.equals("NA")) { + System.out.println(" to " + getCategory()); + } else { + System.out.println(); + assert category.equals("NA"); + } + } + } + + /** + * Performs an edit on the item if the item already exists in the item list + * Only the item information that are different will be edited + * + * @param itemName The name of the Item to be edited. + */ + + public void updateItemInfo(String itemName) { + System.out.println("Item already exists and item information has been updated"); + int indexOfItem = -1; + for (Item item : Itemlist.getItems()) { + if (item.getItemName().toLowerCase().equals(itemName.toLowerCase())) { + indexOfItem = Itemlist.getIndex(item); + break; + } + } + assert indexOfItem != -1; + Item item = Itemlist.getItem(indexOfItem); + assert item != null; + int currentQty = item.getQuantity(); + int newQty = getQuantity() + currentQty; + String newUOM = (getUnitOfMeasurement().equals(item.getUnitOfMeasurement())) ? "NA" : getUnitOfMeasurement(); + String newCat = (getCategory().equals(item.getCategory())) ? "NA" : getCategory(); + float newBuyPrice = (getBuyPrice() == (item.getBuyPrice())) ? -1 : getBuyPrice(); + float newSellPrice = (getSellPrice() == (item.getSellPrice())) ? -1 : getSellPrice(); + new EditCommand(getItemName(), "NA", newQty, newUOM, newCat, newBuyPrice, + newSellPrice).execute(); + } +} diff --git a/src/main/java/command/AddPromotionCommand.java b/src/main/java/command/AddPromotionCommand.java new file mode 100644 index 0000000000..a81e54723a --- /dev/null +++ b/src/main/java/command/AddPromotionCommand.java @@ -0,0 +1,57 @@ +//@@author HengShuHong +package command; + +import exceptions.CommandFormatException; +import exceptions.InvalidDateException; +import promotion.Month; +import promotion.Promotion; +import promotion.Promotionlist; +import ui.TextUi; + +import java.util.logging.Level; + +public class AddPromotionCommand extends Command { + + protected Promotion promotion; + + public AddPromotionCommand( + String itemName, + Float discount, + int startDate, Month startMonth, int startYear, + int endDate, Month endMonth, int endYear, + int startTime, + int endTime) { + this.promotion = new Promotion( + itemName, discount, + startDate, startMonth, startYear, + endDate, endMonth, endYear, + startTime, endTime); + LOGGER.info("AddPromotion successfully created."); + } + + /** + * Adds a new promotion for an item that is in the item list + * + * @throws InvalidDateException if there is an invalid date for promotion + * @throws CommandFormatException if item is not found in the list or there is an invalid discount + */ + @Override + public void execute() throws InvalidDateException, CommandFormatException { + try { + Promotionlist.addPromotion(promotion); + TextUi.replyToUser( + "The following promotion has been added", + promotion.getItemName() + " have a " + String.format("%.2f", promotion.getDiscount() * 100) + + "% discount", "Period: " + promotion.getStartDay() + " " + promotion.getStartMonth() + + " " + promotion.getStartYear() + " to " + promotion.getEndDay() + " " + + promotion.getEndMonth() + " " + promotion.getEndYear(), + "Time: " + String.format("%04d", promotion.getStartTime()) + " to " + + String.format("%04d", promotion.getEndTime()) + ); + LOGGER.info("Promotion successfully created."); + } catch (InvalidDateException | CommandFormatException e){ + System.out.print(""); + LOGGER.log(Level.WARNING, "Unable to create promotion", e); + } + } +} diff --git a/src/main/java/command/BestsellerCommand.java b/src/main/java/command/BestsellerCommand.java new file mode 100644 index 0000000000..6b9ba2d764 --- /dev/null +++ b/src/main/java/command/BestsellerCommand.java @@ -0,0 +1,25 @@ +package command; + +import exceptions.EmptyListException; +import itemlist.Cashier; +import ui.TextUi; + +public class BestsellerCommand extends Command { + + + //@author LowTL + @Override + public void execute() throws EmptyListException { + String bs = Cashier.getBestseller(); + try { + if (bs == null) { + throw new EmptyListException("Bestseller"); + } + } catch (EmptyListException e) { + LOGGER.warning("No transaction found."); + return; + } + TextUi.replyToUser("The current best-selling item is " + + bs); + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..3bee49edac --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,14 @@ +package command; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; + +import java.util.logging.Logger; + +public abstract class Command { + + protected static final Logger LOGGER = Logger.getLogger(Command.class.getName()); + public abstract void execute() throws CommandFormatException, InvalidDateException, EmptyListException; + +} diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java new file mode 100644 index 0000000000..d56226d41c --- /dev/null +++ b/src/main/java/command/DeleteCommand.java @@ -0,0 +1,61 @@ +package command; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import item.Item; +import itemlist.Itemlist; +import promotion.Promotionlist; +import storage.Storage; + +import java.util.logging.Level; + +public class DeleteCommand extends Command { + + protected String itemName; + + public DeleteCommand(String itemName) { + this.itemName = itemName.toLowerCase(); //for checking later + } + + + public String getItemName() { + return itemName; + } + + /** + * Searches of the item in the item list with the same name and deletes it + */ + public void execute() throws EmptyListException { + int index = -1; + try { + for (Item item : Itemlist.getItems()) { + if (item.getItemName().toLowerCase().equals(itemName.toLowerCase())) { + index = Itemlist.getItems().indexOf(item); + break; + } + } + if (index == -1) { + //throw exception; + throw new CommandFormatException("ITEM_NOT_FOUND"); + } + if (Promotionlist.itemIsOnPromo(itemName)) { + throw new CommandFormatException("UNABLE_TO_DELETE"); + } + Itemlist.deleteItem(index); + System.out.println(itemName + " has been successfully deleted."); + Storage.overwriteFile(Itemlist.getItems()); + if (index == Itemlist.getItems().size()) { + assert (Itemlist.getItem(index) == null); + } else { + assert (!Itemlist.getItem(index).getItemName().equals(itemName)); + } + LOGGER.info("Item successfully deleted."); + } catch (IndexOutOfBoundsException e) { + LOGGER.log(Level.WARNING, "Item not deleted.", e); + System.out.println("Itemlist is empty."); + } catch (CommandFormatException e) { + LOGGER.info("Promotion exists for item and thus cannot be deleted."); + } + } + +} diff --git a/src/main/java/command/DeletePromotionCommand.java b/src/main/java/command/DeletePromotionCommand.java new file mode 100644 index 0000000000..c7137da2bb --- /dev/null +++ b/src/main/java/command/DeletePromotionCommand.java @@ -0,0 +1,39 @@ +package command; + +import common.Messages; +import exceptions.CommandFormatException; +import promotion.Promotion; +import promotion.Promotionlist; +import storage.PromotionStorage; +import ui.TextUi; + +public class DeletePromotionCommand extends Command { + + protected String itemName; + + public DeletePromotionCommand(String itemName) { + this.itemName = itemName; + } + public String getItemName() { + return itemName; + } + + /** + * Deletes a promotion from the promotion list + * + * @throws CommandFormatException is thrown when the promotion for the item does not exists + */ + @Override + public void execute() throws CommandFormatException { + if (Promotionlist.itemIsOnPromo(itemName)) { + Promotion promotion = Promotionlist.getPromotion(itemName); + int index = Promotionlist.getIndex(promotion); + Promotionlist.deletePromotion(index); + TextUi.replyToUser("Promotion for " + itemName + " has been removed"); + PromotionStorage.overwritePromotionFile(Promotionlist.getAllPromotion()); + } else { + System.out.println(Messages.ITEM_NOT_ON_PROMO); + } + } + +} diff --git a/src/main/java/command/EditCommand.java b/src/main/java/command/EditCommand.java new file mode 100644 index 0000000000..1e0b1a5b1d --- /dev/null +++ b/src/main/java/command/EditCommand.java @@ -0,0 +1,124 @@ +//@@author Fureimi +package command; + +import item.Item; +import itemlist.Itemlist; +import promotion.Promotion; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; + +public class EditCommand extends Command{ + + protected String itemName; + protected String newItemName; + protected int newQuantity; + protected String newUnitOfMeasurement; + protected String newCategory; + protected float newBuyPrice; + protected float newSellPrice; + + public EditCommand(String itemName, String newItemName, int newQuantity, String newUnitOfMeasurement, + String newCategory, + float newBuyPrice, float newSellPrice) { + this.itemName = itemName; + this.newItemName = newItemName; + this.newQuantity = newQuantity; + this.newUnitOfMeasurement = newUnitOfMeasurement; + this.newCategory = newCategory; + this.newBuyPrice = newBuyPrice; + this.newSellPrice = newSellPrice; + } + + public String getItemName() { + return itemName; + } + + public String getNewItemName() { + return newItemName; + } + + public int getNewQuantity() { + return newQuantity; + } + + public String getNewUnitOfMeasurement() { + return newUnitOfMeasurement; + } + + public String getNewCategory() { + return newCategory; + } + + public float getNewBuyPrice() { + return newBuyPrice; + } + + public float getNewSellPrice() { + return newSellPrice; + } + + /** + * Edits the relevant params of the item in the item list and prints out to the user. + */ + @Override + public void execute() { + int index = -1; // flag to check if item exists in Itemlist + for (Item item : Itemlist.getItems()) { + if (item.getItemName().equals(itemName) || item.getItemName().toLowerCase().equals(itemName)) { + index = Itemlist.getItems().indexOf(item); + break; + } + } + if (index == -1) { // if item is not found + //throw exception; + LOGGER.warning("Item not found."); + ui.TextUi.replyToUser("item not found!"); + } else { + Item item = Itemlist.getItem(index); + assert item != null; + String itemName = item.getItemName(); + ui.TextUi.replyToUser("\n" + "Edited: "); + if (!newItemName.equals("NA")) { // check if itemName was edited + ui.TextUi.showEditMessage(itemName, "newItemName", itemName, newItemName); + item.setItemName(newItemName); + if (Promotionlist.itemIsOnPromo(itemName)) { + Promotion promotion = Promotionlist.getPromotion(itemName); + assert promotion != null; + promotion.setItemName(newItemName); + PromotionStorage.overwritePromotionFile(Promotionlist.getAllPromotion()); + } + } + if (newQuantity != -1) { // check if quantity was edited + ui.TextUi.showEditMessage(itemName, "newQuantity", String.valueOf(item.getQuantity()), + String.valueOf(newQuantity)); + item.setQuantity(newQuantity); + } + if (!newUnitOfMeasurement.equals("NA")) { // check if unitOfMeasurement was edited + ui.TextUi.showEditMessage(itemName, "newUnitOfMeasurement", item.getUnitOfMeasurement(), + newUnitOfMeasurement); + item.setUnitOfMeasurement(newUnitOfMeasurement); + } + if (!newCategory.equals("NA")) { // check if category was edited + ui.TextUi.showEditMessage(itemName, "newCategory", String.valueOf(item.getCategory()), + String.valueOf(newCategory)); + item.setCategory(newCategory); + } + if (newBuyPrice != -1) { // check if buyPrice was edited + ui.TextUi.showEditMessage(itemName, "newBuyPrice", String.valueOf(item.getBuyPrice()), + String.format("%.2f", newBuyPrice)); + item.setBuyPrice(newBuyPrice); + } + if (newSellPrice != -1) { // check if sellPrice was edited + ui.TextUi.showEditMessage(itemName, "newSellPrice", String.valueOf(item.getSellPrice()), + String.format("%.2f", newSellPrice)); + item.setSellPrice(newSellPrice); + } + } + ui.TextUi.replyToUser("End of Edits"); + LOGGER.info("Edit run successfully."); + ui.TextUi.replyToUser(""); + Storage.overwriteFile(Itemlist.getItems()); + } + +} diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java new file mode 100644 index 0000000000..7d8a3f3d1b --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,18 @@ +package command; + +public class ExitCommand extends Command{ + + protected static boolean isExit = false; + + public ExitCommand(boolean isExit) { + ExitCommand.isExit = isExit; + } + + public static boolean getIsExit() { + return isExit; + } + + @Override + public void execute() { + } +} diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java new file mode 100644 index 0000000000..377ce44643 --- /dev/null +++ b/src/main/java/command/FindCommand.java @@ -0,0 +1,120 @@ +//@@author HengShuHong +package command; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import item.Item; +import itemlist.Itemlist; +import ui.TextUi; + +import java.util.ArrayList; + +public class FindCommand extends Command { + + protected String keyword; + + protected String itemInfo; + + public FindCommand(String itemInfo, String keyword) { + this.keyword = keyword.toLowerCase(); + this.itemInfo = itemInfo; + } + + public String getItemInfo() { + return itemInfo; + } + + public String getKeyword() { + return keyword; + } + + /** + * Searches for item(s) given a specified keyword + * + * @throws EmptyListException when the filtered list is empty (nothing found) + */ + @Override + public void execute() throws EmptyListException { + if (itemInfo.equals("NA")) { + itemInfo = "item/qty/uom/cat/buy/sell"; + } + try { + ArrayList searchList = filterList(); + TextUi.showList(searchList); + LOGGER.info("Itemlist successfully filtered."); + } catch (EmptyListException e) { + LOGGER.warning("EMPTY LIST"); + } catch (CommandFormatException e) { + LOGGER.warning("INVALID FILTER"); + } + } + + /** + * Enhances the find feature by searching through a filtered list, e.g. search for "fruits" in /item /uom + * + * @return a list of items that matches the filter and the keyword + * @throws EmptyListException when the filtered list is empty (nothing found) + */ + public ArrayList filterList() throws EmptyListException, CommandFormatException { + ArrayList searchList = new ArrayList<>(); + String [] getFilter = itemInfo.split("/"); + for (String s : getFilter) { + String filter = s.toLowerCase(); + switch (filter) { + case "item": //fall through + case "qty": //fall through + case "uom": //fall through + case "cat": //fall through + case "buy": //fall through + case "sell": + filterItemsBySingleFilter(searchList, filter, keyword); + break; + default: + throw new CommandFormatException("INVALID_FILTER"); + } + } + if (searchList.isEmpty()) { + LOGGER.warning("Item not found."); + throw new EmptyListException("Item"); + } + return searchList; + } + + private void filterItemsBySingleFilter(ArrayList searchList, String filter, String keyword) { + for (Item item : Itemlist.getItems()) { + if (searchList.contains(String.valueOf(item))) { + continue; + } + if (filter.equalsIgnoreCase("item") && item.getItemName().toLowerCase().contains(keyword)) { + searchList.add(String.valueOf(item)); + continue; + } + if (filter.equalsIgnoreCase("qty") && Integer.toString(item.getQuantity()).equals(keyword)) { + searchList.add(String.valueOf(item)); + continue; + } + if (filter.equalsIgnoreCase("uom") && item.getUnitOfMeasurement().toLowerCase().contains(keyword)) { + searchList.add(String.valueOf(item)); + continue; + } + if (item.getCategory() != null && filter.equalsIgnoreCase("cat") && item.getCategory().toLowerCase(). + contains(keyword)) { + searchList.add(String.valueOf(item)); + continue; + } + try { + if (filter.equalsIgnoreCase("buy") && Math.abs(item.getBuyPrice() - Float.parseFloat(keyword)) + <= 0.01f) { + searchList.add(String.valueOf(item)); + continue; + } + if (filter.equalsIgnoreCase("sell") && Math.abs(item.getSellPrice() - Float.parseFloat(keyword)) + <= 0.01f) { + searchList.add(String.valueOf(item)); + } + } catch (NumberFormatException e) { + LOGGER.info("Keyword contains non numeric characters"); + } + } + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..867130562a --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,83 @@ +package command; + +import common.HelpMessages; + +public class HelpCommand extends Command{ + + protected String command; + + public HelpCommand(String command){ + this.command = command; + LOGGER.info("Command successfully created"); + } + + /** + * Prints the help message for the relevant command + */ + @Override + public void execute() { + switch (command) { + case "NA": + System.out.println(HelpMessages.HELP); + break; + case "list items": + System.out.println(HelpMessages.HELP_LIST_ITEMS); + break; + case "add": + System.out.println(HelpMessages.HELP_ADD); + break; + case "sell": + System.out.println(HelpMessages.HELP_SELL); + break; + case "edit": + System.out.println(HelpMessages.HELP_EDIT); + break; + case "find": + System.out.println(HelpMessages.HELP_FIND); + break; + case "mark": + System.out.println(HelpMessages.HELP_MARK); + break; + case "unmark": + System.out.println(HelpMessages.HELP_UNMARK); + break; + case "delete": + System.out.println(HelpMessages.HELP_DEL); + break; + case "bestseller": + System.out.println(HelpMessages.HELP_BESTSELLER); + break; + case "total profit": + System.out.println(HelpMessages.HELP_TOTAL_PROFIT); + break; + case "total revenue": + System.out.println(HelpMessages.HELP_TOTAL_REVENUE); + break; + case "promotion": + System.out.println(HelpMessages.HELP_PROMOTION); + break; + case "delete promotion": + System.out.println(HelpMessages.HELP_DEL_PROMO); + break; + case "list promotion": + System.out.println(HelpMessages.HELP_LIST_PROMO); + break; + case "low stock": + System.out.println(HelpMessages.HELP_LOW_STOCK); + break; + case "exit": + System.out.println(HelpMessages.HELP_EXIT); + break; + case "list transactions": + System.out.println(HelpMessages.HELP_LIST_TRANSACTIONS); + break; + default: + System.out.println(HelpMessages.INVALID_HELP_COMMAND); + LOGGER.warning("Invalid help command received."); + break; + } + + + + } +} diff --git a/src/main/java/command/IncorrectCommand.java b/src/main/java/command/IncorrectCommand.java new file mode 100644 index 0000000000..b08e1f6083 --- /dev/null +++ b/src/main/java/command/IncorrectCommand.java @@ -0,0 +1,14 @@ +package command; + + +public class IncorrectCommand extends Command { + + public IncorrectCommand() { + LOGGER.warning("Incorrect Command received."); + } + + @Override + public void execute() { + } + +} diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java new file mode 100644 index 0000000000..18effc13e6 --- /dev/null +++ b/src/main/java/command/ListCommand.java @@ -0,0 +1,167 @@ +package command; + +import exceptions.EmptyListException; +import item.Item; +import item.Transaction; +import itemlist.Cashier; +import promotion.Promotion; +import ui.TextUi; + +import java.util.ArrayList; +import java.util.logging.Level; + +/** + * Prints out the various ArrayLists, full or partial, based on modifiers. + * It prints differently based on the Type of ArrayList used. + * ie Item, Transaction or Promotion + */ +public class ListCommand extends Command{ + + protected ArrayList itemList = new ArrayList<>(); + protected ArrayList transactionList = new ArrayList<>(); + protected ArrayList promotionList = new ArrayList<>(); + protected String category = "NA"; + protected boolean isListMarked = false; + + /** + * Instantiates a ListCommand with the ArrayList arrayList + * and any other modifiers available to that type of ArrayList. + */ + public ListCommand(ArrayList arrayList, String category, boolean isListMarked) { + this.itemList= arrayList; + this.category = category; + this.isListMarked = isListMarked; + LOGGER.info("List items command generated."); + try { + if (arrayList == null || arrayList.isEmpty()) { + if (category.equals("NA") && !isListMarked) { + throw new EmptyListException("Item"); + } else { + throw new EmptyListException("Filter Item"); + } + } + } catch (EmptyListException e) { + LOGGER.warning("Empty item list."); + } + } + + /** + * Instantiates a ListCommand with the ArrayList arrayList + * and any other modifiers available to that type of ArrayList. + */ + public ListCommand(ArrayList arrayList, String itemName) { + if (itemName.equals("NA")) { + this.transactionList = arrayList; + } else { + this.transactionList = Cashier.getTransactions(itemName); + } + try { + if (transactionList == null || transactionList.isEmpty()) { + if (itemName.equals("NA")) { + throw new EmptyListException("Transaction"); + } else { + throw new EmptyListException("Filter Transaction"); + } + } + } catch (EmptyListException e) { + LOGGER.warning("Empty list detected."); + } + } + + /** + * Instantiates a ListCommand with the ArrayList arrayList + * and any other modifiers available to that type of ArrayList. + */ + public ListCommand(ArrayList arrayList) { + try { + if (arrayList.isEmpty()) { + throw new EmptyListException("Promotion"); + } + } catch (EmptyListException e) { + LOGGER.warning("Empty list detected."); + } + this.promotionList = arrayList; + } + + public String getCategory() { + return category; + } + + /** + * Runs the list command with 3 cases + * Depending on which ArrayList is not empty, it prints that list. + * */ + //@@author Fureimi + public void execute() { + + if (containsTransactions(transactionList)) { + showTransactionList(); + LOGGER.info("Transactions listed."); + } else if (containsPromotions(promotionList)) { + showPromotionList(); + LOGGER.info("Promotions listed."); + } else if (containsItems(itemList) && (!category.equals("NA") || isListMarked)) { + showCustomizedItemList(); + LOGGER.info("Customised item listed."); + } else if (containsItems(itemList) && category.equals("NA") && !isListMarked) { + TextUi.showList(itemList); + LOGGER.info("All item listed."); + } + } + + private void showTransactionList() { + TextUi.showTransactionList(transactionList); + } + + private void showCustomizedItemList() { + TextUi.showCustomizedList(itemList, category, isListMarked); + } + + private void showPromotionList() { + TextUi.showPromotionList(promotionList); + } + + private static boolean containsItems(ArrayList arrayList) { + try { + for (Object obj : arrayList) { + if (obj instanceof Item) { + return true; + } + } + } catch (NullPointerException e) { + LOGGER.log(Level.WARNING, "NullPointerException occurred.", e); + return false; + } + return false; + } + + private static boolean containsTransactions(ArrayList arrayList) { + try { + for (Object obj : arrayList) { + if (obj instanceof Transaction) { + return true; + } + } + } catch (NullPointerException e) { + LOGGER.log(Level.WARNING, "NullPointerException occurred.", e); + return false; + } + return false; + } + + private static boolean containsPromotions(ArrayList arrayList) { + try { + for (Object obj : arrayList) { + if (obj instanceof Promotion) { + return true; + } + } + } catch (NullPointerException e) { + LOGGER.log(Level.WARNING, "NullPointerException occurred.", e); + return false; + } + return false; + } + +} + diff --git a/src/main/java/command/LowStockCommand.java b/src/main/java/command/LowStockCommand.java new file mode 100644 index 0000000000..cea4ad7c5a --- /dev/null +++ b/src/main/java/command/LowStockCommand.java @@ -0,0 +1,56 @@ +package command; + +import item.Item; +import itemlist.Itemlist; + +public class LowStockCommand extends Command{ + + protected static final int DEFAULT_LOW_STOCK_THRESHOLD = 10; + protected int lowStockAmount; + + public LowStockCommand(){ + this.lowStockAmount = DEFAULT_LOW_STOCK_THRESHOLD; + } + public LowStockCommand(int lowStockAmount){ + this.lowStockAmount = lowStockAmount; + } + + + public int getAmount() { + return lowStockAmount; + } + + private static void outOfStockItemsReminder(){ + int count = 0; + System.out.println("Out-of-stock Items:"); + for (Item item : Itemlist.getItems()) { + if (item.getIsOOS()) { + System.out.println(item.getItemName()); + count++; + } + } + if (count == 0){ + System.out.println("No items out of stock"); + } + } + + public static void lowOnStockItemsReminder(int lowStockAmount){ + int count = 0; + System.out.println("Low-on-stock Items: (less than " + lowStockAmount + ")"); + for (Item item : Itemlist.getItems()) { + if (item.getQuantity() <= lowStockAmount && item.getQuantity() > 0) { //low stock condition + System.out.println(item.getItemName()); + count++; + } + } + if (count == 0){ + System.out.println("No items low on stock"); + } + } + @Override + public void execute() { + outOfStockItemsReminder(); + lowOnStockItemsReminder(lowStockAmount); + } + +} diff --git a/src/main/java/command/MarkCommand.java b/src/main/java/command/MarkCommand.java new file mode 100644 index 0000000000..f9df799b7f --- /dev/null +++ b/src/main/java/command/MarkCommand.java @@ -0,0 +1,35 @@ +package command; + +import exceptions.CommandFormatException; +import itemlist.Itemlist; +import item.Item; +import storage.Storage; + + +public class MarkCommand extends Command { + + protected String itemName; + + public MarkCommand(String itemName) { + this.itemName = itemName; + } + + public String getItemName() { + return itemName; + } + + @Override + public void execute() throws CommandFormatException { + Item item = Itemlist.getItem(itemName); + if (item == null) { + ui.TextUi.replyToUser("Item of item name '" + itemName + "' not found! Please try again."); + } else if (item.isMark) { + ui.TextUi.replyToUser("Item is already marked!"); + } else { + item.mark(); + ui.TextUi.replyToUser("Successfully marked " + item + "!"); + } + Storage.overwriteFile(Itemlist.getItems()); + } + +} diff --git a/src/main/java/command/SellCommand.java b/src/main/java/command/SellCommand.java new file mode 100644 index 0000000000..79807b0b55 --- /dev/null +++ b/src/main/java/command/SellCommand.java @@ -0,0 +1,74 @@ +package command; + +import exceptions.CommandFormatException; +import item.Item; +import item.Transaction; +import itemlist.Cashier; +import itemlist.Itemlist; +import storage.Storage; +import storage.TransactionLogs; + +import java.util.Objects; + +public class SellCommand extends Command { + + protected String itemName; + protected int sellQuantity; + protected float discount; + + public SellCommand (String itemName, int quantity, float discount) { + this.itemName = itemName; + this.sellQuantity = quantity; + this.discount = discount; + } + + public String getItemName() { + return itemName; + } + + public int getSellQuantity() { + return sellQuantity; + } + + /** + * Checks that the item is in the list, then edits the quantity, reducing it by the amount sold + */ + @Override + public void execute() throws CommandFormatException { + int index = -1; + Item toSell = null; + for (Item item : Itemlist.getItems()) { + if (item.getItemName().toLowerCase().equals(itemName.toLowerCase())) { + index = Itemlist.getItems().indexOf(item); + toSell = item; + break; + } + } + if (index == -1) { + //throw exception; + LOGGER.warning("Item not found."); + System.out.println("Item not found!"); + return; + } + assert (Objects.nonNull(Itemlist.getItem(index))); + int remainingQuantity = Itemlist.getItem(index).getQuantity() - sellQuantity; + float getSellPrice = Itemlist.getItem(index).getSellPrice(); + float sellPrice = (this.discount > 0) ? (1-this.discount) * getSellPrice : getSellPrice; + if (toSell.getIsOOS() || remainingQuantity < 0) { + LOGGER.warning("Item has insufficient quantity."); + System.out.println("There is insufficient stock!"); + return; + } else { + ui.TextUi.showSellMessage(itemName, sellQuantity, remainingQuantity, sellPrice); + Itemlist.editQuantity(index, remainingQuantity); + LOGGER.info("Item successfully sold."); + } + Storage.overwriteFile(Itemlist.getItems()); + Transaction newTransaction = new Transaction(Itemlist.getItem(index).getItemName(), + sellQuantity, toSell.getBuyPrice(), sellPrice); + Cashier.addItem(newTransaction); + TransactionLogs.addToLog(Cashier.getTransactions()); + LOGGER.info("Transaction successfully recorded."); + } + +} diff --git a/src/main/java/command/TotalProfitCommand.java b/src/main/java/command/TotalProfitCommand.java new file mode 100644 index 0000000000..9c5cd163c6 --- /dev/null +++ b/src/main/java/command/TotalProfitCommand.java @@ -0,0 +1,22 @@ +package command; + +import itemlist.Cashier; +import parser.CommandType; +import ui.TextUi; + +public class TotalProfitCommand extends Command { + CommandType command; + public TotalProfitCommand(CommandType command) { + this.command = command; + } + @Override + public void execute() { + float totalProfit = command.equals(CommandType.TOTAL_PROFIT) ? + Cashier.getTotalProfit() : Cashier.getTotalRevenue(); + if (totalProfit == 0) { + return; + } + TextUi.replyToUser("You have earned " + totalProfit + + " in "+ (command.equals(CommandType.TOTAL_PROFIT) ? "profits" : "revenue") + " so far."); + } +} diff --git a/src/main/java/command/UnmarkCommand.java b/src/main/java/command/UnmarkCommand.java new file mode 100644 index 0000000000..43a38ee326 --- /dev/null +++ b/src/main/java/command/UnmarkCommand.java @@ -0,0 +1,35 @@ +package command; + +import exceptions.CommandFormatException; +import itemlist.Itemlist; +import item.Item; +import storage.Storage; + + +public class UnmarkCommand extends Command { + + protected String itemName; + + public UnmarkCommand(String itemName) { + this.itemName = itemName; + } + + public String getItemName() { + return itemName; + } + + @Override + public void execute() throws CommandFormatException { + Item item = Itemlist.getItem(itemName); + if (item == null) { + ui.TextUi.replyToUser("Item of item name '" + itemName + "' not found! Please try again."); + } else if (!item.isMark) { + ui.TextUi.replyToUser("Item is already unmarked!"); + } else { + item.unmark(); + ui.TextUi.replyToUser("Successfully unmarked " + item + "!"); + } + Storage.overwriteFile(Itemlist.getItems()); + } + +} diff --git a/src/main/java/common/HelpMessages.java b/src/main/java/common/HelpMessages.java new file mode 100644 index 0000000000..c46e320f3f --- /dev/null +++ b/src/main/java/common/HelpMessages.java @@ -0,0 +1,243 @@ +package common; + +public class HelpMessages { + + public static final String HELP = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| list items | list_items [marked] [cat/CATEGORY] |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| list | list_transactions [item/ITEM_NAME] |\n" + + "| transaction| |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| add | add ITEM_NAME qty/QUANTITY_OF_ITEM /UNIT_OF_MEASUREMENT [cat/CATEGORY] |\n" + + "| | buy/BUY_PRICE sell/SELL_PRICE |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| sell | sell ITEM_NAME qty/SELL_QUANTITY |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| edit | edit ITEM_NAME [name/NEW_NAME] [qty/NEW_QUANTITY] [uom/NEW_UOM] |\n" + + "| | [cat/NEW_CATEGORY] [buy/NEW_BUY_PRICE] [SELL/NEW_SELL_PRICE] |\n" + + "| | (use AT LEAST 1 of: name/ qty/, uom/, cat/, buy/, sell/) |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| mark | mark ITEM_NAME |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| unmark | unmark ITEM_NAME |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| delete | del ITEM_NAME |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| find | 1. find KEYWORD - to search the entire Item List |\n" + + "| | 2. find /FILTER1/FILTER2 KEYWORD - to search under the filters* |\n" + + "| | * (filters: item, qty, uom, cat, buy, sell) |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| bestseller | bestseller |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| total | total_profit |\n" + + "| profit | |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| total | total_revenue |\n" + + "| revenue | |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| promotion | promotion ITEM_NAME discount/DISCOUNT period /from DD MMM YYYY |\n" + + "| | to DD MMM YYYY time /from TIME /to TIME |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| delete | del_promo ITEM_NAME |\n" + + "| promotion | |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| list | list_promotions |\n" + + "| promotions | |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| low stock | low_stock /AMOUNT |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| exit | exit |\n" + + "|____________|______________________________________________________________________________|\n" + + "* type help c/COMMAND for more detailed explanations\n" + + " (use the command names on the left column)\n" + + "* parameters in [] are optional and can be omitted."; + + public static final String HELP_EXIT = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| exit | exit |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_LOW_STOCK = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| low stock | low_stock /AMOUNT |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_LIST_PROMO = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| list | list_promotions |\n" + + "| promotions | |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_DEL_PROMO = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| delete | del_promo ITEM_NAME |\n" + + "| promotion | |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_PROMOTION = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| promotion | promotion ITEM_NAME discount/DISCOUNT period /from DD MMM YYYY |\n" + + "| | to DD MMM YYYY time /from TIME /to TIME |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| example: | promotion milk discount/5 period /from 03 MAR 2024 to 09 MAR 2024 |\n" + + "| | time /from 0000 /to 2359 |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| remarks: | discount is a percentage discount of the original sell price |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_TOTAL_REVENUE = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| total | total_revenue |\n" + + "| revenue | |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_TOTAL_PROFIT = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| total | total_profit |\n" + + "| profit | |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_BESTSELLER = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| bestseller | bestseller |\n" + + "|____________|______________________________________________________________________________|\n"; + public static final String HELP_FIND = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| find | 1. find KEYWORD - to search the entire Item List |\n" + + "| | 2. find /FILTER1/FILTER2 KEYWORD - to search under the filters* |\n" + + "| | * (filters: item, qty, uom, cat, buy, sell) |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| example: | find apple |\n" + + "| | find /cat fruit |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_LIST_ITEMS = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| list items | list_items [marked] [cat/CATEGORY] |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_MARK = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| mark | mark ITEM_NAME |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_UNMARK = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| unmark | unmark ITEM_NAME |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_DEL = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| delete | del ITEM_NAME |\n" + + "|____________|______________________________________________________________________________|\n"; + public static final String HELP_EDIT = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| edit | edit ITEM_NAME [name/NEW_NAME] [qty/NEW_QUANTITY] [uom/NEW_UOM] |\n" + + "| | [cat/NEW_CATEGORY] [buy/NEW_BUY_PRICE] [SELL/NEW_SELL_PRICE] |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| example: | edit apple qty/15 /pcs cat/fruit buy/2.50 sell/3.50 |\n" + + "| | edit plastic bag qty/150 |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| remarks: | use AT LEAST 1 of: qty/, uom/, cat/, buy/, sell/ |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_SELL = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| sell | sell ITEM_NAME qty/SELL_QUANTITY |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| example: | sell apple qty/5 |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_ADD = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|------------|------------------------------------------------------------------------------|\n" + + "| add | add ITEM_NAME qty/QUANTITY_OF_ITEM /UNIT_OF_MEASUREMENT [cat/CATEGORY] |\n" + + "| | buy/BUY_PRICE sell/SELL_PRICE |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| example: | add apple qty/100 /pcs cat/fruit buy/1.50 sell/2.50 |\n" + + "| | add flour qty/15 /kg buy/5.10 sell/7.45 |\n" + + "| |------------------------------------------------------------------------------|\n" + + "| remarks: | category is optional |\n" + + "|____________|______________________________________________________________________________|\n"; + + public static final String HELP_LIST_TRANSACTIONS = + " ___________________________________________________________________________________________\n" + + "| STOCKMASTER |\n" + + "|___________________________________________________________________________________________|\n" + + "| Commands | Format |\n" + + "|-------------------|-----------------------------------------------------------------------|\n" + + "| list_transactions | list_transaction [item/ITEM_NAME] |\n" + + "|-------------------|-----------------------------------------------------------------------|\n" + + "| example: | list_transactions item/apple |\n" + + "|___________________|_______________________________________________________________________|\n"; + + public static final String INVALID_HELP_COMMAND = "Please input a valid command to inquire about."; +} diff --git a/src/main/java/common/Messages.java b/src/main/java/common/Messages.java new file mode 100644 index 0000000000..e4f64061b1 --- /dev/null +++ b/src/main/java/common/Messages.java @@ -0,0 +1,105 @@ +package common; + +public class Messages { + public static final String INVALID_COMMAND = "Invalid command detected. Type 'help' for list of valid commands"; + + public static final String INVALID_HELP_FORMAT = "Invalid command format. Please use format: 'help' or " + + "'help c/COMMAND'"; + public static final String INVALID_ADD_FORMAT = "Invalid command format. Please use format: " + "\n" + + "'add ITEM_NAME qty/QUANTITY_OF_ITEM /UNIT_OF_MEASUREMENT [cat/CATEGORY] " + + "buy/BUY_PRICE sell/SELL_PRICE'"; + public static final String INVALID_DELETE_FORMAT = "Invalid command format. Please use format: 'del [ITEM_NAME]'"; + public static final String INVALID_EDIT_FORMAT = "Invalid edit command format. Please use format: " + + "'edit ITEM_NAME [name/NEW_NAME] [qty/NEW_QUANTITY] [uom/NEW_UOM] [cat/NEW_CATEGORY] " + + "[buy/NEW_BUY_PRICE] [sell/NEW_SELL_PRICE]'\n" + "You can edit at least 1 parameter up to all available" + + " parameters. For example, if you only wish to update buy and sell price, you can input:\n" + + "'edit ITEM_NAME buy/NEW_BUY_PRICE sell/NEW_SELL_PRICE'"; + public static final String INVALID_SELL_FORMAT = "Invalid command format. Please use format: " + + "'sell ITEM_NAME qty/SELL_QUANTITY'"; + public static final String ITEM_NOT_FOUND = "Item cannot be found/unavailable"; + + public static final String ITEM_NOT_ON_PROMO = "Promotion does not exists for this item and hence " + + "cannot be deleted"; + public static final String INVALID_FIND_FORMAT = "Invalid command format. Please use format: " + "\n" + + "1. 'find KEYWORD' to search the entire Item List" + "\n" + + "2. 'find /filter1/filter2 KEYWORD' to search under the filters"; + + public static final String UNABLE_TO_DELETE = "There is a promotion that exists for this item. Please remove the " + + "promotion before deleting the item."; + public static final String EMPTY_ITEM_LIST = "There are no items at the moment."; + public static final String EMPTY_TRANSACTION_LIST = "There are no transactions at the moment."; + public static final String EMPTY_FILTERED_ITEM_LIST = "There are no items with your search query."; + public static final String EMPTY_FILTERED_TRANSACTION_LIST = "There are no transactions with your search query."; + public static final String EMPTY_LIST = "There is nothing here! Time to spend some money and stock em up!"; + public static final String WELCOME_MESSAGE = "Welcome to StockMaster, where you can master the knowledge on your " + + "Stock!"; + public static final String GOODBYE_MESSAGE = "Thank you for using StockMaster, hope we have helped your lazy ass!"; + + public static final String INVALID_PERIOD = "Invalid Period Format. Please ensure that range is valid" + + "\n" + "e.g. period /from 1 Jan 2024 /to 10 Jan 2024"; + + public static final String INVALID_TIME = "Invalid Time Format. Please ensure that the time is in 24 hours " + + "format and have a valid range:" + "\n" + "e.g. time /from 0000 /to 2359"; + + public static final String INVALID_FILTER = "Invalid Filter is used. Filters can only be " + + "[item, qty, cat, uom, buy, sell] and must be in the following format /filter1/filter2 etc."; + public static final String INVALID_DATE = "Invalid Date has been entered. Please ensure that the date exists."; + public static final String INVALID_MONTH = "Invalid Month has been entered. Please use format: MMM (e.g. JAN, DEC)"; + + public static final String INVALID_DISCOUNT = "Invalid Discount has been entered. " + + "Please ensure it falls within the " + "range of 0 to 100"; + + public static final String INVALID_MARK_FORMAT = "Invalid command format. Please use format: " + + "'mark ITEM_NAME'"; + + public static final String INVALID_UNMARK_FORMAT = "Invalid command format. Please use format: " + + "'unmark ITEM_NAME'"; + + public static final String INVALID_LIST_FORMAT = "Invalid Command Format. Please use format:\n" + + "1. 'list_items' to list all items in the inventory, \n" + + "2. 'list_items cat/CATEGORY' to list all items in that category, or \n" + + "3. 'list_items marked' to list all marked items, or \n" + + "4. 'list_items marked cat/CATEGORY' to list all marked items in that category.\n" + + "Please note that marked must be in front of cat/CATEGORY."; + + public static final String INVALID_DEL_PROMO_FORMAT = "Invalid Command Format. Please use format: " + + "del_promo [ITEM_NAME]"; + public static final String INVALID_PROMOLIST_FORMAT = "Invalid Command Format. Please use format: " + + "list_promotions"; + + + public static final String ITEM_IS_PROMO = "Item already has a promotion. Please remove the current promotion to " + + "add a new one."; + + public static final String INVALID_PROMOTION_FORMAT = "Invalid Command Format. Please use format:\n" + + "promotion ITEM_NAME discount/DISCOUNT period /from START_DATE /to END_DATE time /from START_TIME" + + " /to END_TIME"; + + public static final String INVALID_LOW_STOCK_FORMAT = "Invalid Command Format. Please use format: " + + "low_stock /AMOUNT"; + + public static final String INVALID_ITEM_NAME = "Invalid item name. Please input an item name."; + + public static final String INVALID_CATEGORY = "Blank category detected. Please input a category."; + + public static final String INVALID_UNITS = "Invalid units of measurement."; + + public static final String QTY_TOO_LARGE = "Quantity is too large. Please input a smaller quantity."; + + public static final String BUY_TOO_LARGE = "Buy price is too large. Please input a smaller buy price."; + + public static final String SELL_TOO_LARGE = "Sell price is too large. Please input a smaller sell price."; + + public static final String INVALID_TRANSACTION_FORMAT = "Invalid format. Please input the format:" + + "list_transactions [item/ITEM_NAME]"; + public static final String INVALID_LOW_STOCK_AMOUNT = "Please input a valid amount."; + + public static final String NEGATIVE_LOW_STOCK_AMOUNT = "Amount should be greater than 0."; + + public static final String INVALID_VALUE = "Please input a valid amount."; + + public static final String NO_BESTSELLER = "There are no transactions. Please add a transaction " + + "before retrying the command."; + + public static final String EMPTY_PROMOTION_LIST = "There are no promotions at the moment."; +} diff --git a/src/main/java/exceptions/CommandFormatException.java b/src/main/java/exceptions/CommandFormatException.java new file mode 100644 index 0000000000..a1425c020f --- /dev/null +++ b/src/main/java/exceptions/CommandFormatException.java @@ -0,0 +1,110 @@ +package exceptions; + +import common.HelpMessages; +import parser.CommandType; +import common.Messages; + +public class CommandFormatException extends Exception{ + public CommandFormatException(CommandType command){ + + switch (command) { + + case ADD: + System.out.println(Messages.INVALID_ADD_FORMAT); + break; + case DEL: + System.out.println(Messages.INVALID_DELETE_FORMAT); + break; + case EDIT: + System.out.println(Messages.INVALID_EDIT_FORMAT); + break; + case SELL: + System.out.println(Messages.INVALID_SELL_FORMAT); + break; + case FIND: + System.out.println(Messages.INVALID_FIND_FORMAT); + break; + case HELP: + System.out.println(Messages.INVALID_HELP_FORMAT); + break; + case LIST_ITEMS: + System.out.println(Messages.INVALID_LIST_FORMAT); + break; + case LIST_PROMOTIONS: + System.out.println(Messages.INVALID_PROMOLIST_FORMAT); + break; + case DEL_PROMO: + System.out.println(Messages.INVALID_DEL_PROMO_FORMAT); + break; + case PROMOTION: + System.out.println(Messages.INVALID_PROMOTION_FORMAT); + break; + case MARK: + System.out.println(Messages.INVALID_MARK_FORMAT); + break; + case UNMARK: + System.out.println(Messages.INVALID_UNMARK_FORMAT); + break; + case LIST_TRANSACTIONS: + System.out.println(Messages.INVALID_TRANSACTION_FORMAT); + break; + case LOW_STOCK: + System.out.println(Messages.INVALID_LOW_STOCK_FORMAT); + break; + default: + System.out.println(Messages.INVALID_COMMAND); + break; + } + } + + public CommandFormatException(String error) { + switch (error) { + + case "ITEM_NOT_FOUND": + System.out.println(Messages.ITEM_NOT_FOUND); + break; + case "INVALID_DISCOUNT": + System.out.println(Messages.INVALID_DISCOUNT); + break; + case "INVALID_FILTER": + System.out.println(Messages.INVALID_FILTER); + break; + case "INVALID_ITEM_NAME": + System.out.println(Messages.INVALID_ITEM_NAME); + break; + case "INVALID_CATEGORY": + System.out.println(Messages.INVALID_CATEGORY); + break; + case "INVALID_UNITS": + System.out.println(Messages.INVALID_UNITS); + break; + case "INVALID_COMMAND": + System.out.println(HelpMessages.INVALID_HELP_COMMAND); + break; + case "UNABLE_TO_DELETE": + System.out.println(Messages.UNABLE_TO_DELETE); + break; + case "QTY_TOO_LARGE": + System.out.println(Messages.QTY_TOO_LARGE); + break; + case "BUY_TOO_LARGE": + System.out.println(Messages.BUY_TOO_LARGE); + break; + case "SELL_TOO_LARGE": + System.out.println(Messages.SELL_TOO_LARGE); + break; + case "INVALID_LOW_STOCK_AMOUNT": + System.out.println(Messages.INVALID_LOW_STOCK_AMOUNT); + break; + case "NEGATIVE_LOW_STOCK_AMOUNT": + System.out.println(Messages.NEGATIVE_LOW_STOCK_AMOUNT); + break; + case "INVALID_VALUE": + System.out.println(Messages.INVALID_VALUE); + break; + default: + System.out.println(error); + } + + } +} diff --git a/src/main/java/exceptions/EditException.java b/src/main/java/exceptions/EditException.java new file mode 100644 index 0000000000..6db7bf2be6 --- /dev/null +++ b/src/main/java/exceptions/EditException.java @@ -0,0 +1,36 @@ +package exceptions; + +import ui.TextUi; + +public class EditException extends Exception { + public EditException(String error) { + switch (error) { + case "ITEM_ALREADY_EXISTS": + TextUi.replyToUser("New item name cannot be the same as an existing item!"); + break; + case "ITEM_NAME": + TextUi.replyToUser("New item name cannot be blank!"); + break; + case "QUANTITY": + TextUi.replyToUser("Quantity cannot be negative!"); + break; + case "UNIT_OF_MEASUREMENT": + TextUi.replyToUser("New unit of measurement should not be empty!"); + break; + case "CATEGORY": + TextUi.replyToUser("New category should not be empty!"); + break; + case "BUY_PRICE": + TextUi.replyToUser("New buy price should be larger than 0!"); + break; + case "SELL_PRICE": + TextUi.replyToUser("New sell price should be larger than 0!"); + break; + default: + TextUi.replyToUser("Error with edits!"); + break; + } + + } + +} diff --git a/src/main/java/exceptions/EmptyListException.java b/src/main/java/exceptions/EmptyListException.java new file mode 100644 index 0000000000..06b9a0c2fe --- /dev/null +++ b/src/main/java/exceptions/EmptyListException.java @@ -0,0 +1,35 @@ +package exceptions; + + +import common.Messages; + +public class EmptyListException extends Exception { + public EmptyListException(String error) { + switch (error) { + case "Filter Item": + System.out.println(Messages.EMPTY_FILTERED_ITEM_LIST); + break; + case "Item": + System.out.println(Messages.EMPTY_ITEM_LIST); + break; + case "Filter Transaction": + System.out.println(Messages.EMPTY_FILTERED_TRANSACTION_LIST); + break; + case "Transaction": + System.out.println(Messages.EMPTY_TRANSACTION_LIST); + break; + case "Promotion": + System.out.println(Messages.EMPTY_PROMOTION_LIST); + break; + case "Empty List": + System.out.println("No results found."); + break; + case "Bestseller": + System.out.println(Messages.NO_BESTSELLER); + break; + default: + System.out.println(error); + } + } + +} diff --git a/src/main/java/exceptions/InvalidDateException.java b/src/main/java/exceptions/InvalidDateException.java new file mode 100644 index 0000000000..2a6bef3c12 --- /dev/null +++ b/src/main/java/exceptions/InvalidDateException.java @@ -0,0 +1,29 @@ +//@@author HengShuHong +package exceptions; + +import common.Messages; + +public class InvalidDateException extends Exception { + public InvalidDateException(String error) { + switch(error) { + case "INVALID_PERIOD": + System.out.println(Messages.INVALID_PERIOD); + break; + case "INVALID_DATE": + System.out.println(Messages.INVALID_DATE); + break; + case "INVALID_TIME": + System.out.println(Messages.INVALID_TIME); + break; + case "ITEM_IS_PROMO": + System.out.println(Messages.ITEM_IS_PROMO); + break; + case "INVALID_MONTH": + System.out.println(Messages.INVALID_MONTH); + break; + default: + System.out.println("Error Detected"); + break; + } + } +} diff --git a/src/main/java/item/Item.java b/src/main/java/item/Item.java new file mode 100644 index 0000000000..3e5bb9e18b --- /dev/null +++ b/src/main/java/item/Item.java @@ -0,0 +1,119 @@ +//@@author Fureimi +package item; + +import itemlist.Itemlist; + +public class Item { + public boolean isMark; + public boolean isOOS; + private String itemName; + private int quantity; + private String unitOfMeasurement; + private String category; + private float buyPrice; + private float sellPrice; + + public Item(String name, int quantity, String unitOfMeasurement, String category, float buyPrice, float sellPrice) { + this.itemName = name; + this.quantity = quantity; + assert quantity>= 0 : "Quantity should not be negative."; + this.unitOfMeasurement = unitOfMeasurement; + if (category.isEmpty()) { + this.category = "NA"; + } else { + this.category = category; + } + this.buyPrice = buyPrice; + this.sellPrice = sellPrice; + this.isOOS = quantity == 0; + Itemlist.noOfItems++; + + } + + public String getCategory() { + if (this.category.equals("NA")) { + return null; + } else { + return this.category; + } + } + + public void setCategory(String newCategory) { + this.category = newCategory; + } + public String getItemName() { + return this.itemName; + } + + public void setItemName(String newName) { + this.itemName = newName; + } + + public String getUnitOfMeasurement() { + return unitOfMeasurement; + } + + public void setUnitOfMeasurement(String newUnitOfMeasurement) { + this.unitOfMeasurement = newUnitOfMeasurement; + } + + public int getQuantity() { + return this.quantity; + } + + public void setQuantity(int newQuantity) { + this.quantity = newQuantity; + if (newQuantity == 0) { + this.isOOS = true; + } + } + + public float getBuyPrice() { + return buyPrice; + } + + public void setBuyPrice(float newBuyPrice) { + this.buyPrice = newBuyPrice; + } + + public float getSellPrice() { + return sellPrice; + } + + public void setSellPrice(float newSellPrice) { + this.sellPrice = newSellPrice; + } + + public boolean getIsOOS() { + return isOOS; + } + + public void markOOS() { + this.isOOS = true; + } + + public void unmarkOOS() { + this.isOOS = false; + } + + public void mark() { + this.isMark = true; + } + + public void unmark() { + this.isMark = false; + } + + public boolean getMarkStatus() { + return this.isMark; + } + + @Override + public String toString() { + String categoryString = (getCategory() != null) ? ", Category: " + getCategory() : ""; + String markString = (this.isMark) ? "[X] " : "[ ] "; + return (markString + getItemName() + " (Qty: " + getQuantity() + " " + getUnitOfMeasurement() + + ", Buy: $" + String.format("%.2f", getBuyPrice()) + ", Sell: $" + String.format("%.2f", getSellPrice()) + + categoryString + ")"); + } +} diff --git a/src/main/java/item/Transaction.java b/src/main/java/item/Transaction.java new file mode 100644 index 0000000000..c675fea320 --- /dev/null +++ b/src/main/java/item/Transaction.java @@ -0,0 +1,71 @@ +package item; + + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class Transaction { + private String dateTime; + private float totalPrice; + private float profit; + private String item; + private int quantity; + private float buyPrice; + private float sellPrice; + + public Transaction(String name, int inputQty, float inputBuy, float inputSell) { + setDateTime(); + item = name; + quantity = inputQty; + buyPrice = inputBuy; + sellPrice = inputSell; + totalPrice = sellPrice * quantity; + profit = totalPrice - buyPrice * quantity; + } + + public Transaction(String name, int inputQty, float inputBuy, float inputSell, String storedTime) { + dateTime = storedTime; + item = name; + quantity = inputQty; + buyPrice = inputBuy; + sellPrice = inputSell; + totalPrice = sellPrice * quantity; + profit = totalPrice - buyPrice * quantity; + } + + public String getItemName() { + return this.item; + } + public int getQuantity() { + return this.quantity; + } + + public float getSellPrice() { + return this.sellPrice; + } + + public String getDateTime() { + return this.dateTime; + } + + public float getTotalPrice() { + return this.totalPrice; + } + + public float getProfit() { + return this.profit; + } + + + public void setDateTime() { + LocalDateTime currentTime = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + this.dateTime = currentTime.format(formatter); + } + + @Override + public String toString() { + return (this.quantity + " " + this.getItemName() + + " Sell: $" + this.sellPrice + " Date: " + this.getDateTime() ); + } +} diff --git a/src/main/java/itemlist/Cashier.java b/src/main/java/itemlist/Cashier.java new file mode 100644 index 0000000000..7e1c572dea --- /dev/null +++ b/src/main/java/itemlist/Cashier.java @@ -0,0 +1,108 @@ +package itemlist; + +import exceptions.EmptyListException; +import item.Transaction; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class Cashier extends Itemlist { + + public static ArrayList transactions = new ArrayList<>(); + protected static final Logger LOGGER = Logger.getLogger(Cashier.class.getName()); + + public static void addItem(Transaction transaction) { + transactions.add(transaction); + } + + public static void deleteItem(int index) { + transactions.remove(index); + } + + public static ArrayList getTransactions() { + return transactions; + } + //Overloading of function allows for getting of Transactions with the specific itemName + public static ArrayList getTransactions(String itemName) { + ArrayList results = new ArrayList<>(); + if (!transactions.isEmpty()) { + for (Transaction t : transactions) { + if (t.getItemName().equals(itemName)) { + results.add(t); + } + } + } + return results; + } + + public static float getTotalRevenue() { + float totalRevenue = 0; + try { + ArrayList allTransactions = getTransactions(); + if (allTransactions.isEmpty()) { + throw new EmptyListException("Bestseller"); + } + for (Transaction t : allTransactions) { + totalRevenue += t.getTotalPrice(); + } + } catch (EmptyListException e) { + LOGGER.warning("No transactions found."); + return 0; + } + return totalRevenue; + } + + public static float getTotalProfit() { + float totalProfit = 0; + try { + if (transactions.isEmpty()) { + throw new EmptyListException("Bestseller"); + } + for (Transaction t : transactions) { + totalProfit += t.getProfit(); + } + } catch (EmptyListException e) { + LOGGER.warning("No transactions found."); + return 0; + } + return totalProfit; + } + + public static Transaction getTransaction(int index) { + try { + return transactions.get(index); + } catch (IndexOutOfBoundsException e) { + if (index == 0) { + LOGGER.warning("No transactions found."); + System.out.println("No transactions found."); + } else { + LOGGER.warning("Index out of bounds."); + System.out.println("Index " + index + " entered is out of bound."); + } + return null; + } + } + + public static String getBestseller() { + if (transactions.isEmpty()) { + return null; + } + String bestSeller = transactions.get(0).getItemName(); + ArrayList tempNames = new ArrayList<>(); + float[] profits = new float[transactions.size()]; + assert(Itemlist.noOfItems > 0); + for (Transaction t: transactions) { + if (!tempNames.contains(t.getItemName())) { + tempNames.add(t.getItemName()); + } + profits[tempNames.indexOf(t.getItemName())] += t.getProfit(); + } + for (int i = 1; i < tempNames.size(); i++) { + if (profits[i] > profits[tempNames.indexOf(bestSeller)]) { + System.out.println(tempNames.get(i)); + bestSeller = tempNames.get(i); + } + } + return bestSeller; + } +} diff --git a/src/main/java/itemlist/Itemlist.java b/src/main/java/itemlist/Itemlist.java new file mode 100644 index 0000000000..99d3ad407b --- /dev/null +++ b/src/main/java/itemlist/Itemlist.java @@ -0,0 +1,74 @@ +//@@author Fureimi +package itemlist; +import item.Item; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Itemlist { + public static int noOfItems; + protected static final Logger LOGGER = Logger.getLogger(Itemlist.class.getName()); + protected static ArrayList items = new ArrayList<>(); + + public Itemlist() { + } + public static void addItem(Item item) { + items.add(item); + } + + public static void deleteItem(int index) { + items.remove(index); + noOfItems--; + } + + public static void editQuantity(int index, int newQuantity) { + items.get(index).setQuantity(newQuantity); + if (newQuantity == 0) { + items.get(index).markOOS(); + } else if (newQuantity > 1) { + items.get(index).unmarkOOS(); + } + } + + public static boolean itemIsExist(String itemName) { + for (Item item : Itemlist.getItems()) { + if (item.getItemName().toLowerCase().equals(itemName.toLowerCase())) { + return true; + } + } + return false; + } + public static ArrayList getItems() { + return items; + } + + public static Item getItem(int index) { + try { + return items.get(index); + } catch (IndexOutOfBoundsException e) { + if (index != 0) { + LOGGER.log(Level.WARNING, "Index out of bound", e); + System.out.println("Index " + index + " entered is out of bound."); + } else { + LOGGER.warning("There are no items."); + System.out.println("There are no items added yet!"); + } + return null; + } + } + + public static Item getItem(String name) { + for (Item i: items) { + if (i.getItemName().equals(name)) { + LOGGER.info("Item found."); + return i; + } + } + LOGGER.warning("Item not found."); + return null; + } + public static int getIndex(Item item) { + return items.indexOf(item); + } +} diff --git a/src/main/java/parser/CommandType.java b/src/main/java/parser/CommandType.java new file mode 100644 index 0000000000..2f14586c40 --- /dev/null +++ b/src/main/java/parser/CommandType.java @@ -0,0 +1,23 @@ +package parser; + +public enum CommandType { + EXIT, + LIST_ITEMS, + LIST_PROMOTIONS, + DEL_PROMO, + HELP, + ADD, + DEL, + EDIT, + SELL, + FIND, + PROMOTION, + MARK, + UNMARK, + TOTAL_PROFIT, + TOTAL_REVENUE, + BESTSELLER, + LIST_TRANSACTIONS, + LOW_STOCK + +} diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java new file mode 100644 index 0000000000..d54cda462e --- /dev/null +++ b/src/main/java/parser/Parser.java @@ -0,0 +1,632 @@ +package parser; + +import command.AddCommand; +import command.AddPromotionCommand; +import command.BestsellerCommand; +import command.Command; +import command.DeleteCommand; +import command.DeletePromotionCommand; +import command.EditCommand; +import command.ExitCommand; +import command.FindCommand; +import command.HelpCommand; +import command.IncorrectCommand; +import command.ListCommand; +import command.LowStockCommand; +import command.MarkCommand; +import command.SellCommand; +import command.TotalProfitCommand; +import command.UnmarkCommand; +import common.HelpMessages; +import common.Messages; +import exceptions.CommandFormatException; +import exceptions.EditException; +import exceptions.InvalidDateException; +import item.Item; +import itemlist.Cashier; +import itemlist.Itemlist; +import promotion.Month; +import promotion.Promotionlist; + + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; + +public class Parser { + + /** + * Takes in the user's input and calls the relevant command + * + * @param userInput The user's input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + + private static final Logger LOGGER = Logger.getLogger(Parser.class.getName()); + public Command parseInput(String userInput){ + final CommandType userCommand; + final Matcher matcher = ParserFormat.BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + System.out.println(Messages.INVALID_COMMAND); + System.out.println(HelpMessages.HELP); + LOGGER.log(Level.FINE, "Invalid command received."); + return new IncorrectCommand(); + } + String commandWord = matcher.group("commandWord").toUpperCase(); + try { + userCommand = CommandType.valueOf(commandWord); + } catch (IllegalArgumentException e){ + System.out.println(Messages.INVALID_COMMAND); + return new IncorrectCommand(); + } + switch (userCommand) { + case EXIT: + return new ExitCommand(true); + case HELP: + try { + return prepareHelp(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case LIST_ITEMS: + try { + return prepareItemList(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case LIST_PROMOTIONS: + try { + return preparePromotionList(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case LIST_TRANSACTIONS: + try { + return prepareTransactionList(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case DEL_PROMO: + try { + return prepareDeletePromo(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case ADD: + try { + return prepareAdd(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case DEL: + try { + return prepareDelete(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case EDIT: + try { + return prepareEdit(userInput); + } catch (CommandFormatException | EditException e) { + LOGGER.log(Level.WARNING, "Invalid input detected.", e); + break; + } + case FIND: + try { + return prepareFind(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case SELL: + try { + return prepareSell(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case PROMOTION: + try { + return preparePromotion(userInput); + } catch (CommandFormatException | InvalidDateException e) { + LOGGER.log(Level.WARNING, "Invalid input detected.", e); + break; + } + case MARK: + try { + return prepareMark(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case UNMARK: + try { + return prepareUnmark(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + case TOTAL_PROFIT: + //fallthrough + case TOTAL_REVENUE: + return new TotalProfitCommand(userCommand); + case BESTSELLER: + return new BestsellerCommand(); + case LOW_STOCK: + try { + return prepareLowStock(userInput); + } catch (CommandFormatException e) { + LOGGER.log(Level.WARNING, "Invalid input detected."); + break; + } + default: + System.out.println(Messages.INVALID_COMMAND); + return new IncorrectCommand(); + } + return new IncorrectCommand(); + } + + /** + * Formats the user's input to call HelpCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareHelp(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.HELP_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.HELP); + } + + String command = matcher.group("command") != null ? + matcher.group("command").toLowerCase().trim() : "NA"; + if (command.isEmpty()) { + throw new CommandFormatException("INVALID_HELP_COMMAND"); + } + + return new HelpCommand(command); + } + + /** + * Formats the user's input to call AddCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareAdd(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.ADD_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.ADD); + } + + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + + String category = matcher.group("category") != null ? matcher.group("category").trim() : "NA"; + if (category.isEmpty()) { + throw new CommandFormatException("INVALID_CATEGORY"); + } + + int quantity; + try { + quantity = Integer.parseInt(matcher.group("quantity")); + } catch (NumberFormatException e) { + throw new CommandFormatException("QTY_TOO_LARGE"); + } + + String unitOfMeasurement = matcher.group("unitOfMeasurement").trim(); + if (unitOfMeasurement.isEmpty()) { + throw new CommandFormatException("INVALID_UNITS"); + } + + float buyPrice; + try { + buyPrice = Float.parseFloat(matcher.group("buyPrice")); + } catch (NumberFormatException e) { + throw new CommandFormatException("INVALID_VALUE"); + } + // Check if the parsed quantity is larger than Integer.MAX_VALUE + if (buyPrice > Integer.MAX_VALUE) { + throw new CommandFormatException("BUY_TOO_LARGE"); + } + + float sellPrice; + try { + sellPrice = Float.parseFloat(matcher.group("sellPrice")); + } catch (NumberFormatException e) { + throw new CommandFormatException("INVALID_VALUE"); + } + // Check if the parsed quantity is larger than Integer.MAX_VALUE + if (sellPrice > Integer.MAX_VALUE) { + throw new CommandFormatException("SELL_TOO_LARGE"); + } + + assert quantity >= 0 : "Quantity should not be negative."; + return new AddCommand( + itemName, + quantity, + unitOfMeasurement, + category, + buyPrice, + sellPrice + ); + } + + /** + * Formats the user's input to call DeleteCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareDelete(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.DELETE_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.DEL); + } + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + return new DeleteCommand(itemName); + } + + /** + * Formats the user's input to call SellCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareSell(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.SELL_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.SELL); + } + + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + + int sellQuantity; + try { + sellQuantity = Integer.parseInt(matcher.group("sellQuantity").trim()); + } catch (NumberFormatException e) { + throw new CommandFormatException("QTY_TOO_LARGE"); + } + + if (Promotionlist.isPromoExistNow(matcher.group("itemName"))) { + float getDiscount = (Promotionlist.getPromotion(matcher.group("itemName"))).getDiscount(); + return new SellCommand( + matcher.group("itemName"), + sellQuantity, + getDiscount + ); + } else { + return new SellCommand( + itemName, + sellQuantity, + -1 + ); + } + } + + /** + * Formats the user's input to call FindCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareFind(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.FIND_COMMAND_FORMAT.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.FIND); + } + String itemInfo = matcher.group("itemInfo") != null ? matcher.group("itemInfo").toLowerCase() : "NA"; + return new FindCommand( + itemInfo, + matcher.group("keyword")); + } + + //@@author Fureimi + /** + * Formats the user's input to call EditCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + * @throws EditException Command does not follow the required format for the edit command. + */ + private Command prepareEdit(String args) throws CommandFormatException, EditException { + final Matcher matcher = ParserFormat.EDIT_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.EDIT); + } + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + + // check if itemName was edited. If no, newItemName will be NA + String newItemName = matcher.group("newItemName") != null ? + matcher.group("newItemName").toLowerCase().trim() : "NA"; + + if (newItemName.isBlank() || newItemName.isEmpty()) { + throw new EditException("ITEM_NAME"); + } else { + for (Item item : Itemlist.getItems()) { + if (item.getItemName().equals(newItemName) || item.getItemName().toLowerCase().equals(newItemName)) { + throw new EditException("ITEM_ALREADY_EXISTS"); + } + } + } + // check if quantity was edited. If no, newQuantity will be -1 + int newQuantity; + try { + newQuantity = matcher.group("newQuantity") != null ? + Integer.parseInt(matcher.group("newQuantity")) : -1; + if (matcher.group("newQuantity") != null && newQuantity < 0) { + throw new EditException("QUANTITY"); + } + } catch (NumberFormatException e) { + throw new CommandFormatException("QTY_TOO_LARGE"); + } + + // check if unitOfMeasurement was edited. If no, newUnitOfMeasurement will be NA + String newUnitOfMeasurement = matcher.group("newUnitOfMeasurement") != null ? + matcher.group("newUnitOfMeasurement") : "NA"; + if (newUnitOfMeasurement.isEmpty() || newUnitOfMeasurement.isBlank()) { + throw new EditException("UNIT_OF_MEASUREMENT"); + } + + // check if category was edited. If no, newCategory will be NA + String newCategory = matcher.group("newCategory") != null ? matcher.group("newCategory") : "NA"; + if (newCategory.isBlank() || newCategory.isEmpty()) { + throw new EditException("CATEGORY"); + } + + // check if BuyPrice was edited. If no, newBuyPrice will be -1 + float newBuyPrice; + try { + newBuyPrice = matcher.group("newBuyPrice") != null ? + Float.parseFloat(matcher.group("newBuyPrice")) : -1; + if (matcher.group("newBuyPrice") != null && newBuyPrice < 0) { + throw new EditException("BUY_PRICE"); + } + if (newBuyPrice >= Integer.MAX_VALUE){ + throw new CommandFormatException("BUY_TOO_LARGE"); + } + } catch (NumberFormatException e) { + throw new CommandFormatException("BUY_TOO_LARGE"); + } + + // check if sellPrice was edited. If no, newSellPrice will be -1 + float newSellPrice; + try { + newSellPrice = matcher.group("newSellPrice") != null ? + Float.parseFloat(matcher.group("newSellPrice")) : -1; + if (matcher.group("newSellPrice") != null && newSellPrice < 0) { + throw new EditException("SELL_PRICE"); + } + if (newSellPrice >= Integer.MAX_VALUE){ + throw new CommandFormatException("SELL_TOO_LARGE"); + } + } catch (NumberFormatException e) { + throw new CommandFormatException("SELL_TOO_LARGE"); + } + return new EditCommand( + itemName, + newItemName, + newQuantity, + newUnitOfMeasurement, + newCategory, + newBuyPrice, + newSellPrice + ); + } + + /** + * Formats the user's input to call AddPromotionCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + * @throws InvalidDateException Date/time input is not valid. + */ + private Command preparePromotion(String args) throws CommandFormatException, InvalidDateException { + final Matcher matcher = ParserFormat.PROMOTION_COMMAND_FORMAT.matcher(args.trim()); + + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.PROMOTION); + } + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + float discount = Float.parseFloat(matcher.group("discount")) / 100; + int startDate = Integer.parseInt(matcher.group("startDate")); + String startMonth = matcher.group("startMonth"); + int startYear = Integer.parseInt(matcher.group("startYear")); + int endDate = Integer.parseInt(matcher.group("endDate")); + String endMonth = matcher.group("endMonth"); + int endYear = Integer.parseInt(matcher.group("endYear")); + int startTime = Integer.parseInt(matcher.group("startTime")); + int endTime = Integer.parseInt(matcher.group("endTime")); + try { + Month startMonthEnum = Month.valueOf(startMonth.toUpperCase()); + Month endMonthEnum = Month.valueOf(endMonth.toUpperCase()); + return new AddPromotionCommand( + itemName, + discount, + startDate, + Month.valueOf(startMonth.toUpperCase()), + startYear, + endDate, + Month.valueOf(endMonth.toUpperCase()), + endYear, + startTime, + endTime + ); + } catch (IllegalArgumentException e) { + throw new InvalidDateException("INVALID_MONTH"); + } + } + + /** + * Formats the user's input to call DeletePromotionCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareDeletePromo(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.DELETE_PROMO_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.DEL_PROMO); + } + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + return new DeletePromotionCommand(itemName); + } + + /** + * Formats the user's input to list all items or list them by category + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareItemList(String args) throws CommandFormatException { + final Matcher matcher = ParserFormat.LIST_ITEM_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.LIST_ITEMS); + } + String category = matcher.group("category") != null ? matcher.group("category").toLowerCase().trim() : "NA"; + if (category.isEmpty()) { + throw new CommandFormatException("INVALID_CATEGORY"); + } + boolean listMarked = matcher.group("isMark") != null; + return new ListCommand(Itemlist.getItems(), category, listMarked); + } + + private Command preparePromotionList(String args) throws CommandFormatException { + final Matcher matcher = ParserFormat.LIST_PROMOTION_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.LIST_PROMOTIONS); + } + return new ListCommand(Promotionlist.getAllPromotion()); + } + + /** + * Formats the user's input to call MarkCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareMark(String args) throws CommandFormatException { + final Matcher matcher = ParserFormat.MARK_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.MARK); + } + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + return new MarkCommand(itemName); + } + + /** + * Formats the user's input to call UnmarkCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareUnmark(String args) throws CommandFormatException { + final Matcher matcher = ParserFormat.UNMARK_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.UNMARK); + } + String itemName = matcher.group("itemName").toLowerCase().trim(); + if (itemName.isEmpty()) { + throw new CommandFormatException("INVALID_ITEM_NAME"); + } + return new UnmarkCommand(itemName); + } + + /** + * Formats the user's input to list all transactions or list all transactions of a certain item + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareTransactionList(String args) throws CommandFormatException { + final Matcher matcher = ParserFormat.LIST_TRANSACTION_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.LIST_TRANSACTIONS); + } + if (matcher.group("itemName") == null) { + return new ListCommand(Cashier.getTransactions(), "NA"); + } + + String itemName = matcher.group(1).trim(); + return new ListCommand(Cashier.getTransactions(), itemName); + } + + /** + * Formats the user's input to call LowStockCommand with the appropriate params + * + * @param args The user's parsed input command + * + * @throws CommandFormatException Command does not follow the required format. + */ + private Command prepareLowStock(String args) throws CommandFormatException{ + final Matcher matcher = ParserFormat.LOW_STOCK_COMMAND_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + throw new CommandFormatException(CommandType.LOW_STOCK); + } + + int amount; + try { + amount = Integer.parseInt(matcher.group("amount")); + if (amount <= 0){ + throw new CommandFormatException("NEGATIVE_LOW_STOCK_AMOUNT"); + } + } catch (NumberFormatException e) { + throw new CommandFormatException("INVALID_LOW_STOCK_AMOUNT"); + } + + return new LowStockCommand(amount); + } + +} + + + diff --git a/src/main/java/parser/ParserFormat.java b/src/main/java/parser/ParserFormat.java new file mode 100644 index 0000000000..b0c0ca6777 --- /dev/null +++ b/src/main/java/parser/ParserFormat.java @@ -0,0 +1,56 @@ +package parser; + +import java.util.regex.Pattern; + +public class ParserFormat { + + public static final Pattern HELP_COMMAND_FORMAT = + Pattern.compile("help(?: c/(?[^/]+))?"); + public static final Pattern ADD_COMMAND_FORMAT = + Pattern.compile("add (?[^/]+) qty/(?\\d+) /(?[^/]+)" + + "(?: cat/(?[^/]+))? buy/(?\\d*\\.?\\d+) sell/(?\\d*\\.?\\d+)"); + + public static final Pattern LIST_PROMOTION_COMMAND_FORMAT = + Pattern.compile("list_promotions\\s*$"); + + public static final Pattern DELETE_COMMAND_FORMAT = + Pattern.compile("del (?[^/]+)"); + + public static final Pattern EDIT_COMMAND_FORMAT = + Pattern.compile("edit (?[^/]+)" + + "(?:\\s+(name/(?[^/]+)|qty/(?\\d+)|uom/(?[^/]+)|" + + "cat/(?[^/]+)|buy/(?\\d*\\.?\\d+)|sell/(?\\d*\\.?\\d+)))+"); + + public static final Pattern SELL_COMMAND_FORMAT = + Pattern.compile("sell (?[^/]+) qty/(?\\d+)"); + + public static final Pattern FIND_COMMAND_FORMAT = + Pattern.compile("find(?:\\s/(?[^/]+(?:/[^/]+)*))?\\s(?[^/]+)"); + + public static final Pattern BASIC_COMMAND_FORMAT = + Pattern.compile("(?\\S+)(?.*)"); + + public static final Pattern LIST_ITEM_COMMAND_FORMAT = + Pattern.compile("list_items(?:\\s+(?marked))?(?:\\s+cat/(?[^/]+))?"); + + public static final Pattern MARK_COMMAND_FORMAT = + Pattern.compile("mark (?[^/]+)"); + public static final Pattern UNMARK_COMMAND_FORMAT = + Pattern.compile("unmark (?[^/]+)"); + + public static final Pattern PROMOTION_COMMAND_FORMAT = + Pattern.compile("promotion (?[^/]+) discount/(?\\d+(\\.\\d{1,2})?) " + + "period /from (?\\d+) (?\\w+) (?\\d+) " + + "/to (?\\d+) (?\\w+) (?\\d+) " + + "time /from (?\\d{4}) /to (?\\d{4})"); + public static final Pattern DELETE_PROMO_COMMAND_FORMAT = + Pattern.compile("del_promo (?[^/]+)"); + + public static final Pattern LIST_TRANSACTION_COMMAND_FORMAT = + Pattern.compile("^list_transactions(?:\\s+item/(?\\w+))?$"); + + + public static final Pattern LOW_STOCK_COMMAND_FORMAT = + Pattern.compile("low_stock /(?[^/]+)"); + +} diff --git a/src/main/java/promotion/Month.java b/src/main/java/promotion/Month.java new file mode 100644 index 0000000000..9cde1cb6d5 --- /dev/null +++ b/src/main/java/promotion/Month.java @@ -0,0 +1,26 @@ +package promotion; + +public enum Month { + JAN(1), + FEB(2), + MAR(3), + APR(4), + MAY(5), + JUN(6), + JUL(7), + AUG(8), + SEP(9), + OCT(10), + NOV(11), + DEC(12); + + private final int value; + + Month(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/promotion/Promotion.java b/src/main/java/promotion/Promotion.java new file mode 100644 index 0000000000..1940a3726c --- /dev/null +++ b/src/main/java/promotion/Promotion.java @@ -0,0 +1,99 @@ +//@@author HengShuHong +package promotion; + +public class Promotion { + + protected String itemName; + + protected Float discount; + + protected int startDay; + + protected Month startMonth; + + protected int startYear; + + protected int endDay; + + protected Month endMonth; + + protected int endYear; + + protected int startTime; + + protected int endTime; + + public Promotion( + String itemName, + Float discount, + int startDay, Month startMonth, int startYear, + int endDay, Month endMonth, int endYear, + int startTime, + int endTime) { + this.itemName = itemName; + this.discount = discount; + this.startDay = startDay; + this.startMonth = startMonth; + this.startYear = startYear; + this.endDay = endDay; + this.endMonth = endMonth; + this.endYear = endYear; + this.startTime = startTime; + this.endTime = endTime; + + } + + + public float getDiscount() { + return discount; + } + + public int getStartDay() { + return startDay; + } + + public Month getStartMonth() { + return startMonth; + } + + public int getStartYear() { + return startYear; + } + + public int getEndDay() { + return endDay; + } + + public Month getEndMonth() { + return endMonth; + } + + public int getEndYear() { + return endYear; + } + + public int getStartTime() { + return startTime; + } + + public int getEndTime() { + return endTime; + } + + public String getItemName() { + return itemName; + } + + public void setItemName(String itemName) { + this.itemName = itemName; + } + + @Override + public String toString() { + return getItemName() + " have a " + String.format("%.2f", (getDiscount() * 100)) + + "% discount" + "\n" + "Period: " + getStartDay() + " " + getStartMonth() + " " + + getStartYear() + " to " + getEndDay() + " " + getEndMonth() + " " + + getEndYear() + "\n" + "Time: " + String.format("%04d", getStartTime()) + " to " + + String.format("%04d", getEndTime()); + } +} diff --git a/src/main/java/promotion/Promotionlist.java b/src/main/java/promotion/Promotionlist.java new file mode 100644 index 0000000000..3bb300401e --- /dev/null +++ b/src/main/java/promotion/Promotionlist.java @@ -0,0 +1,278 @@ +//@@author HengShuHong +package promotion; + +import exceptions.CommandFormatException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import storage.PromotionStorage; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.logging.Logger; + +public class Promotionlist { + + protected static final Logger LOGGER = Logger.getLogger(Promotionlist.class.getName()); + private static final ArrayList promotions = new ArrayList<>(); + + public static int getIndex(Promotion promotion) { + return promotions.indexOf(promotion); + } + + public static void deletePromotion(int index) { + promotions.remove(index); + } + + /** + * Boolean to determine if the item has an existing promotion + * + * @param itemName name of item + * @return true if item is on promotion, false if item is not on promotion + */ + public static boolean itemIsOnPromo(String itemName) { + for (Promotion promotion : promotions) { + if (promotion.getItemName().toLowerCase().equals(itemName.toLowerCase())) { + return true; + } + } + return false; + } + + /** + * Checks if the year is a leap year + * + * @param year + * @return true if it is leap year, false if it is not + */ + public static boolean isLeapYear(int year) { + return year % 4 == 0; + } + + /** + * Adds a promotion to promotionlist. + * Multiple checks are done to ensure that the promotion entered is valid. + * + * @param promotion + * @throws InvalidDateException is thrown when the date or time is invalid + * @throws CommandFormatException is thrown when discount is invalid or item is not found + */ + public static void addPromotion(Promotion promotion) throws InvalidDateException, CommandFormatException { + int startDay = promotion.getStartDay(); + int endDate = promotion.getEndDay(); + Month startMonth = promotion.getStartMonth(); + Month endMonth = promotion.getEndMonth(); + int startYear = promotion.getStartYear(); + int endYear = promotion.getEndYear(); + int startTime = promotion.getStartTime(); + int endTime = promotion.getEndTime(); + String itemName = promotion.getItemName(); + float discount = promotion.getDiscount(); + + if (!Itemlist.itemIsExist(itemName)) { + throw new CommandFormatException("ITEM_NOT_FOUND"); + } + if (Promotionlist.itemIsOnPromo(itemName)) { + throw new InvalidDateException("ITEM_IS_PROMO"); + } + if (!isValidDiscount(discount)) { + throw new CommandFormatException("INVALID_DISCOUNT"); + } + if (!isValidMonth(startDay, startMonth, startYear) || !isValidMonth(endDate, endMonth, endYear)) { + throw new InvalidDateException("INVALID_DATE"); + } + if (!isValidTime(startTime, endTime)) { + throw new InvalidDateException("INVALID_TIME"); + } + if (!isValidDuration(startDay, startMonth, startYear, endDate, endMonth, endYear)) { + throw new InvalidDateException("INVALID_PERIOD"); + } + promotions.add(promotion); + PromotionStorage.overwritePromotionFile(Promotionlist.getAllPromotion()); + } + + /** + * Checks if the input discount is valid + * + * @param discount + * @return true if the discount ranges from 0 to 1, returns false otherwise + */ + public static boolean isValidDiscount (float discount) { + return !(discount < 0) && !(discount > 1); + } + + public static boolean isPromoExistNow(String itemName) { + if (!itemIsOnPromo(itemName)) { + return false; + } + Promotion promotion = getPromotion(itemName); + LocalDateTime currentDateTime = LocalDateTime.now(); + + int year = currentDateTime.getYear(); + int month = currentDateTime.getMonthValue(); + int day = currentDateTime.getDayOfMonth(); + int hour = currentDateTime.getHour(); + int minute = currentDateTime.getMinute(); + String formattedTime = String.format("%02d%02d", hour, minute); + int time = Integer.parseInt(formattedTime); + if (time < promotion.getStartTime() || time > promotion.getEndTime()) { + return false; + } + if (year < promotion.getStartYear() || year > promotion.getEndYear()) { + return false; + } + if (year > promotion.getStartYear() && year < promotion.getEndYear()) { + return true; + } + if (year == promotion.getStartYear()) { + if (month < promotion.getStartMonth().getValue()) { + return false; + } + if (day < promotion.getStartDay()) { + return false; + } + } + if (year == promotion.getEndYear()) { + if (month > promotion.getEndMonth().getValue()) { + return false; + } + if (day > promotion.getEndDay()) { + return false; + } + return true; + } + return true; + } + + + /** + * Checks if the time of promotion is valid + * + * @param startTime starting time of the promotion + * @param endTime ending time of the promotion + * @return true if time ranges from 0000 and 2359, and ending time is later than starting time + */ + public static boolean isValidTime(int startTime, int endTime) { + String startTimeStr = String.format("%04d", startTime); + String endTimeStr = String.format("%04d", endTime); + boolean startIsValid = isVerifiedTime(startTimeStr); + boolean endIsValid = isVerifiedTime(endTimeStr); + if (!startIsValid || !endIsValid) { + return false; + } + if (startTime > endTime) { + return false; + } + return true; + } + + /** + * Checks if time ranges from 0000 and 2359 + * + * @param timeStr time with String datatype + * @return true if hour ranges from 0 to 23, minutes ranges from 0 to 59 + */ + public static boolean isVerifiedTime(String timeStr) { + String[] splitTime = timeStr.split("(?<=.)"); + if (splitTime.length != 4) { + return false; + } + int time = Integer.parseInt(timeStr); + int hour = time / 100; + int min = time % 100; + if (hour > 23 || hour < 0) { + return false; + } + return min <= 59 && min >= 0; + } + + + /** + * Checks if the duration of the promotion period is valid + * + * @param startDay + * @param startMonth + * @param startYear + * @param endDay + * @param endMonth + * @param endYear + * @return true if promotion period is valid, return false if end is earlier than the start of promotion + */ + public static boolean isValidDuration (int startDay, Month startMonth, int startYear, int endDay, Month endMonth, + int endYear) { + int startMonthInt = startMonth.getValue(); + int endMonthInt = endMonth.getValue(); + if (endYear > startYear) { + return true; + } else if (endYear < startYear) { + return false; + } + if (endMonthInt > startMonthInt) { + return true; + } else if (endMonthInt < startMonthInt) { + return false; + } + if (endDay > startDay) { + return true; + } + return false; + } + + /** + * Checks if the day exists in the month specified + * + * @param day + * @param month + * @param year + * @return true if day exists, false if does not exists + * @throws InvalidDateException is thrown when the day does not exists in the month + */ + public static boolean isValidMonth(int day, Month month, int year) throws InvalidDateException { + switch (month) { + case FEB: + if (isLeapYear(year) && (day < 30 && day > 0)) { + return true; + } else { + return day <= 28 && day >= 1; + } + case JAN: //fall through + case MAR: //fall through + case MAY: //fall through + case JUL: //fall through + case DEC: //fall through + case OCT: //fall through + case AUG: + return day <= 31 && day >= 1; + case APR: //fall through + case SEP: //fall through + case NOV: //fall through + case JUN: + return day <= 30 && day >= 1; + default: + throw new InvalidDateException("INVALID_PERIOD"); + } + } + + /** + * Gets the promotion for the item + * + * @param itemName + * @return promotion of the item, return null if there is no promotion + */ + public static Promotion getPromotion(String itemName) { + for (Promotion promotion: promotions) { + if (promotion.getItemName().toLowerCase().equals(itemName.toLowerCase())) { + return promotion; + } + } + return null; + } + + /** + * Gets all promotion in the promotion lists + * + * @return all promotions + */ + public static ArrayList getAllPromotion() { + return promotions; + } +} 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/duke/StockMaster.java b/src/main/java/seedu/duke/StockMaster.java new file mode 100644 index 0000000000..dadd2a115e --- /dev/null +++ b/src/main/java/seedu/duke/StockMaster.java @@ -0,0 +1,98 @@ +package seedu.duke; + +import command.Command; +import command.ExitCommand; +import command.LowStockCommand; +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Cashier; +import parser.Parser; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; +import ui.TextUi; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.SimpleFormatter; + + +public class StockMaster { + private static final String STORAGE_FILE = "./StockMasterData.txt"; + private static final String TRANSACTION_FILE = "./TransactionLogs.txt"; + private static final String PROMOTION_STORAGE_FILE = "./PromotionStorage.txt"; + private static final Logger logger = Logger.getLogger(StockMaster.class.getName()); + private final TextUi ui = new TextUi(); + private final Parser parser = new Parser(); + + + + /** + * Main entry-point for the java.duke.StockMaster application. + */ + public static void main(String[] args) throws IOException, CommandFormatException, + InvalidDateException, EmptyListException { + new StockMaster().run(); + } + + public void run() throws IOException, CommandFormatException, InvalidDateException, EmptyListException { + initLogger(); + logger.finest("Run begin"); + ui.showWelcomeMessage("StockMaster v2.0", STORAGE_FILE); + Storage.updateFile("", true); + Storage.readFromFile(STORAGE_FILE); + TransactionLogs.updateFile("", true); + TransactionLogs.readFromFile(TRANSACTION_FILE); + PromotionStorage.updateFile("", true); + PromotionStorage.readFromFile(PROMOTION_STORAGE_FILE); + new LowStockCommand().execute(); + this.normalOperation(); + ui.showGoodByeMessage(STORAGE_FILE, TRANSACTION_FILE, PROMOTION_STORAGE_FILE); + } + + private static void initLogger() { + Logger parserLogger = Logger.getLogger(Parser.class.getName()); + Logger commandLogger = Logger.getLogger(Command.class.getName()); + Logger cashierLogger = Logger.getLogger(Cashier.class.getName()); + Logger storageLogger = Logger.getLogger(Storage.class.getName()); + LogManager.getLogManager().reset(); //clears out any default settings + ConsoleHandler ch = new ConsoleHandler(); //to print errors to console + logger.addHandler(ch); + logger.setLevel(Level.ALL); //to allow all logs to be logged + ch.setLevel(Level.SEVERE); //only print severe logs to console + try { + File dir = new File("logs"); + if (!dir.exists()) { + dir.mkdir(); + } + FileHandler fh = new FileHandler("logs/StockMasterLogs.log"); + fh.setFormatter(new SimpleFormatter()); + fh.setLevel(Level.INFO); //log info and above logs + logger.addHandler(fh); + parserLogger.addHandler(fh); + commandLogger.addHandler(fh); + cashierLogger.addHandler(fh); + storageLogger.addHandler(fh); + } catch (IOException e) { + logger.log(Level.SEVERE, "Unable to create FileHandler", e); + } + } + + private void normalOperation() throws CommandFormatException, + InvalidDateException, EmptyListException { + String userInput; + do { + userInput = TextUi.getUserInput(); + logger.info("Input received: " + userInput); + Command command = parser.parseInput(userInput); + command.execute(); + } while (!ExitCommand.getIsExit()); + } + +} diff --git a/src/main/java/storage/PromotionStorage.java b/src/main/java/storage/PromotionStorage.java new file mode 100644 index 0000000000..c12f0029e0 --- /dev/null +++ b/src/main/java/storage/PromotionStorage.java @@ -0,0 +1,136 @@ +//@@author HengShuHong +package storage; + +import exceptions.CommandFormatException; +import exceptions.InvalidDateException; +import promotion.Month; +import promotion.Promotion; +import promotion.Promotionlist; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.logging.Level; + +/** + * Represents a class that stores and writes information of a list of promotions existed to a file. + * String PROMOTIONSTORAGE represents the designated relative file path for the file. + */ +public class PromotionStorage extends Storage{ + + private static final String PRMOTIONSTORAGE = "./PromotionStorage.txt"; + + public static String getFileDirectory() { + return PRMOTIONSTORAGE; + } + + public static void updateFile(String inputText, boolean ifAppend) { + try { + writeToFile(getFileDirectory(), inputText, ifAppend); + LOGGER.info("Stored promotion."); + } catch (IOException e) { + System.out.println("IOExceptions occurred"); + LOGGER.warning(e.toString()); + } + } + + /** + * Read lines from the file and identify promotions written inside. + * Add the identified promotions into a list of existing promotions. + * + * @param fileName Name of the file to read from. + */ + public static void readFromFile(String fileName) { + int count = 0; + String itemName = ""; + float discount = 0; + int startDate = 0; + int startYear = 0; + int endDate = 0; + int endYear = 0; + Month startMonth = null; + Month endMonth = null; + int startTime = 0; + int endTime = 0; + Scanner scanner = null; + try { + scanner = new Scanner(new File(fileName)); + while (scanner.hasNext()) { + String fileLine = scanner.nextLine(); + switch (count) { + case (0): + String[] itemNameParts = fileLine.split(" have a "); + itemName = itemNameParts[0]; + discount = Float.parseFloat(itemNameParts[1].split("%")[0]) / 100; + count = count + 1; + break; + case (1): + String[] periodParts = fileLine.split("Period: "); + String[] startEndPeriod = periodParts[1].split(" to "); + String[] getStart = startEndPeriod[0].split(" "); + String[] getEnd = startEndPeriod[1].split(" "); + startDate = Integer.parseInt(getStart[0]); + startMonth = Month.valueOf(getStart[1]); + startYear = Integer.parseInt(getStart[2]); + endDate = Integer.parseInt(getEnd[0]); + endMonth = Month.valueOf(getEnd[1]); + endYear = Integer.parseInt(getEnd[2]); + count = count + 1; + break; + case(2): + String[] getTime = fileLine.split("Time: "); + String[] startEndTime = getTime[1].split(" to "); + startTime = Integer.parseInt(startEndTime[0]); + endTime = Integer.parseInt(startEndTime[1]); + Promotion toAdd = new Promotion(itemName, discount, startDate, startMonth, startYear, + endDate, endMonth, endYear, startTime, endTime); + Promotionlist.addPromotion(toAdd); + count = 0; + break; + default: + System.out.println("Read Promotion File Error"); + LOGGER.warning("Promotions not read from file."); + } + } + scanner.close(); + LOGGER.info("Promotions successfully read from file."); + } catch (FileNotFoundException e) { + System.out.println("File does not exist."); + LOGGER.warning("File not found."); + } catch (NumberFormatException e) { + System.out.println("Invalid numbers found."); + LOGGER.warning("Invalid numbers found."); + } catch (InvalidDateException | CommandFormatException e) { + LOGGER.log(Level.WARNING, "Other exception occurred.", e); + System.out.println(e);; + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println(fileName + " is not written in a correct format."); + LOGGER.warning("Incorrect documentation format."); + } + scanner.close(); + } + + /** + * Writes to the indicated file and overwrite previous data + * + * @param promotions List of promotions with relevant details to write + */ + public static void overwritePromotionFile(ArrayList promotions) { + assert promotions != null : "Promotions cannot be null."; + int length = promotions.size(); + for (Promotion promotion: promotions) { + String descriptionAdded = (promotion.getItemName() + " have a " + + String.format("%.2f", (promotion.getDiscount()*100)) + "% discount" + "\n" + "Period: " + + promotion.getStartDay() + " " + promotion.getStartMonth() + " " + promotion.getStartYear() + + " to " + promotion.getEndDay()+ " " + promotion.getEndMonth() + " " + promotion.getEndYear() + + "\n" + "Time: " + promotion.getStartTime() + " to " + promotion.getEndTime() + "\n"); + if (promotions.indexOf(promotion) == 0) { + updateFile(descriptionAdded, false); + } else { + updateFile(descriptionAdded, true); + } + } + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..717f651115 --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,183 @@ +package storage; + +import item.Item; +import itemlist.Itemlist; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Scanner; +import java.io.FileWriter; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Represents a class that stores and writes information of a list of items to a file. + * String FILENAME represents the designated relative file path for the file. + */ +public class Storage { + protected static final Logger LOGGER = Logger.getLogger(Storage.class.getName()); + private static final String FILENAME = "./StockMasterData.txt"; + + + /** + * Write contents to the file. + * + * @param filePath File path where the file is located. + * @param textToAdd The line of text to write to the file. + * @param ifAppend Indicate if append the text at the end of the file (true) + * or overwrite the file (false). + * @throws IOException If file is not found at the indicated file path. + */ + public static void writeToFile(String filePath, String textToAdd, boolean ifAppend) throws IOException { + FileWriter writer = new FileWriter(filePath, ifAppend); + writer.write(textToAdd); + writer.close(); + LOGGER.info("Line written successfully."); + } + + /** + * Update information in file. + * @param inputText The line of text to write to the file. + * @param ifAppend Indicate if append the text at the end of the file (true) + * or overwrite the file (false). + */ + public static void updateFile(String inputText, boolean ifAppend) { + try { + writeToFile(getFileDirectory(), inputText, ifAppend); + LOGGER.info("Line written successfully."); + } catch (IOException e) { + System.out.println("IOExceptions occurred"); + LOGGER.warning("IOException occurred."); + } + } + + /** + * Returns the private file directory of storage. + */ + public static String getFileDirectory() { + return FILENAME; + } + + /** + * Set the private File dukeData. + */ + public static File setFile() { + return new File(FILENAME); + } + + /** + * Write contents to the file. + * + * @param scanner The scanner to read the file. + * @throws NumberFormatException If number is found to be invalid type. + */ + public static void interpretLines(Scanner scanner) throws NumberFormatException{ + while (scanner.hasNext()) { + String fileLine = scanner.nextLine(); + String[] keyCommands = fileLine.split(" \\| "); + String commandQty = ""; + String commandCat = ""; + String commandUom = ""; + String commandBuy = ""; + String commandSell = ""; + String commandName = ""; + String commandIsMarked = ""; + for (String keyCommand : keyCommands) { + if (keyCommand.contains("add")) { + //do nothing. + } else if (keyCommand.contains("Qty: ")) { + String[] commandQtyUnit = keyCommand.replace("Qty: ", "").split(" "); + assert commandQtyUnit.length == 2 : "length not 2!"; + commandQty = commandQtyUnit[0]; + commandUom = commandQtyUnit[1]; + } else if (keyCommand.contains("Cat: ")) { + commandCat = keyCommand.replace("Cat: ", ""); + } else if (keyCommand.contains("BuyPrice: $")) { + commandBuy = keyCommand.replace("BuyPrice: $", ""); + } else if (keyCommand.contains("SellPrice: $")) { + commandSell = keyCommand.replace("SellPrice: $", ""); + } else if (keyCommand.contains("[")) { + commandIsMarked = keyCommand.contains("X") ? "true" : "false"; + } else { + commandName = keyCommand.trim(); + } + } + Item toAdd = new Item(commandName, Integer.parseInt(commandQty), commandUom, commandCat, + Float.parseFloat(commandBuy), Float.parseFloat(commandSell)); + if (commandIsMarked.equals("true")) { + toAdd.mark(); + } else if (commandIsMarked.equals("false")) { + toAdd.unmark(); + } + Itemlist.addItem(toAdd); + LOGGER.info("Item successfully restored."); + } + } + + /** + * Read lines from the file and identify items written inside. + * Add the identified items into a list of existing item lists. + * + * @param fileName Name of the file to read from. + */ + public static void readFromFile(String fileName) { + try { + Scanner scanner = new Scanner(new File(fileName)); + interpretLines(scanner); + scanner.close(); + LOGGER.info("Storage done reading."); + } catch(FileNotFoundException e) { + System.out.println("File does not exist. Creating a new Text File"); + LOGGER.warning("File does not exist."); + } catch(NumberFormatException e) { + System.out.println("Invalid numbers found."); + LOGGER.log(Level.WARNING, "Invalid numbers found.", e); + } + } + + /** + * Writes to the indicated file without overwriting the previous information. + * + * @param items List of items with relevant details to write. + */ + public static void addToFile(ArrayList items) { + assert items != null : "Items cannot be null."; + Item lastItem = items.get(items.size() - 1); + String markString = (lastItem.getMarkStatus()) ? "[X] " : "[ ] "; + String descriptionAdded = (items.size()) + "." + " | " + markString + " | " + + lastItem.getItemName() + " | " + "Qty: " + lastItem.getQuantity() + " " + + lastItem.getUnitOfMeasurement() + " | " + "Cat: " + lastItem.getCategory() + + " | " + "BuyPrice: $" + String.format("%.2f", lastItem.getBuyPrice()) + " | " + + "SellPrice: $" + String.format("%.2f", lastItem.getSellPrice()) + "\n"; + updateFile(descriptionAdded, true); + LOGGER.info("Added line to file."); + } + + /** + * Writes to the indicated file and overwrite previous data. + * + * @param items List of items with relevant details to write. + */ + public static void overwriteFile(ArrayList items) { + assert items != null : "Items cannot be null."; + int length = items.size(); + for (int index = 0; index < length; index++) { + String markString = (items.get(index).getMarkStatus()) ? "[X] " : "[ ] "; + String descriptionAdded = (index + 1) + "." + " | " + markString + " | " + + items.get(index).getItemName() + " | " + "Qty: " + items.get(index).getQuantity() + + " " + items.get(index).getUnitOfMeasurement() + " | " + "Cat: " + + items.get(index).getCategory() + " | " + "BuyPrice: $" + + String.format("%.2f", items.get(index).getBuyPrice()) + " | " + "SellPrice: $" + + String.format("%.2f", items.get(index).getSellPrice()) + "\n"; + if (index == 0) { + updateFile(descriptionAdded, false); + LOGGER.info("File overwritten."); + } else { + updateFile(descriptionAdded, true); + LOGGER.info("File not overwritten."); + } + } + } +} diff --git a/src/main/java/storage/TransactionLogs.java b/src/main/java/storage/TransactionLogs.java new file mode 100644 index 0000000000..c2e56f1c6b --- /dev/null +++ b/src/main/java/storage/TransactionLogs.java @@ -0,0 +1,101 @@ +package storage; + +import item.Transaction; +import itemlist.Cashier; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Represents a class that stores and writes transaction information of a list of items to a file. + * String LOGNAME represents the designated relative file path for the file. + */ +public class TransactionLogs extends Storage { + private static final String LOGNAME = "./TransactionLogs.txt"; + + public static String getFileDirectory() { + return LOGNAME; + } + + public static void updateFile(String inputText, boolean ifAppend) { + try { + writeToFile(getFileDirectory(), inputText, ifAppend); + } catch (IOException e) { + System.out.println("IOExceptions occurred"); + } + } + + /** + * Write contents to the file. + * + * @param scanner The scanner to read the file. + * @throws NumberFormatException If number is found to be invalid type. + */ + public static void interpretLines(Scanner scanner) throws NumberFormatException{ + String commandQty = ""; + String commandProfit = ""; + String commandTotalSell = ""; + String commandSell = ""; + String commandName = ""; + String commandDate = ""; + while (scanner.hasNext()) { + String fileLine = scanner.nextLine(); + if (fileLine.contains("Quantity: ")) { + commandQty = fileLine.replace("Quantity: ", ""); + } else if (fileLine.contains("Date: ")) { + commandDate = fileLine.replace("Date: ", ""); + } else if (fileLine.contains("Unit Price: ")) { + commandSell = fileLine.replace("Unit Price: ", ""); + } else if (fileLine.contains("Total Price: ")) { + commandTotalSell = fileLine.replace("Total Price: ", ""); + } else if (fileLine.contains("Item Name: ")){ + commandName = fileLine.replace("Item Name: ", ""); + } else if (fileLine.contains("Profit: ")) { + commandProfit = fileLine.replace("Profit: ", ""); + int quantityAsInt = Integer.parseInt(commandQty); + float buyAsFloat = (Float.parseFloat(commandTotalSell) - Float.parseFloat(commandProfit)) + / (float) quantityAsInt; + Transaction toAdd = new Transaction(commandName, quantityAsInt, + buyAsFloat, Float.parseFloat(commandSell), commandDate); + Cashier.addItem(toAdd); + LOGGER.info("Transaction added successfully."); + } + } + } + + public static void readFromFile(String fileName) { + try { + Scanner scanner = new Scanner(new File(fileName)); + interpretLines(scanner); + scanner.close(); + } catch(FileNotFoundException e) { + System.out.println("File does not exist."); + } catch(NumberFormatException e) { + System.out.println("Invalid numbers found!!!"); + } + } + + /** + * Writes to the indicated file without overwriting the previous information. + * + * @param transactions List of transactions to write to the file. + */ + public static void addToLog(ArrayList transactions) { + assert transactions != null : "Transactions cannot be null."; + Transaction lastTransaction = transactions.get(transactions.size() - 1); + String descriptionAdded = ""; + descriptionAdded += "Date: " + lastTransaction.getDateTime() + "\n"; + descriptionAdded += "Transaction ID: " + transactions.size() + "\n"; + descriptionAdded += "Item Name: " + lastTransaction.getItemName() + "\n"; + descriptionAdded += "Quantity: " + lastTransaction.getQuantity() + "\n"; + descriptionAdded += "Unit Price: " + lastTransaction.getSellPrice() + "\n"; + descriptionAdded += "Total Price: " + lastTransaction.getTotalPrice() + "\n"; + descriptionAdded += "Profit: " + lastTransaction.getProfit() + "\n"; + descriptionAdded += "\n"; + updateFile(descriptionAdded, true); + LOGGER.info("Stored transaction."); + } +} diff --git a/src/main/java/ui/TextUi.java b/src/main/java/ui/TextUi.java new file mode 100644 index 0000000000..242cbcb781 --- /dev/null +++ b/src/main/java/ui/TextUi.java @@ -0,0 +1,225 @@ +package ui; + +import java.util.ArrayList; +import java.util.Scanner; + +import common.Messages; +import item.Item; +import item.Transaction; +import promotion.Promotion; + +/** + * Represents a class that displays messages and command output to the user. + * String DIVIDER A string to separate current message from the previous ones. + * Scanner in A scanner to retrieve user's text inputs. + */ +public class TextUi { + + public static final String DIVIDER = "----------------"; + + private final Scanner in; + + /** + * Constructor for TextUi. + */ + public TextUi() { + this.in = new Scanner(System.in); + } + + /** + * Retrieve user's input as a string. + */ + public static String getUserInput() { + System.out.println("Enter Command:"); + Scanner in = new Scanner(System.in); + String userInput = in.hasNextLine() ? in.nextLine() : ""; + if (shouldIgnore(userInput)) { + return "Invalid Command"; //Might want to change this with Exceptions + } + return userInput; + } + + /** + * Determines if user's input is empty and should be ignored. + * + * @param userInput User's input. + */ + public static boolean shouldIgnore(String userInput) { + return userInput.trim().isEmpty(); + } + + public void showWelcomeMessage(String version, String storageFilePath) { + replyToUser( + DIVIDER, + version, + DIVIDER, + "Data is being extracted from: " + storageFilePath, + Messages.WELCOME_MESSAGE + ); + } + + /** + * Displays goodbye message to the user when exit the program. + * + * @param storageFilePath Directory of storage file. + * @param transactionLogPath Directory of transaction log file. + * @param promotionStoragePath Directory of promotion storage file. + */ + public void showGoodByeMessage(String storageFilePath, String transactionLogPath, String promotionStoragePath) { + replyToUser( + DIVIDER, + "Inventory is being saved to :" + storageFilePath, + DIVIDER, + "Transactions are being saved to:" + transactionLogPath, + DIVIDER, + "Promotions are being saved to: " + promotionStoragePath, + DIVIDER, + Messages.GOODBYE_MESSAGE + ); + } + + /** + * Displays message to the user. + * + * @param message Message to show to the user. + */ + public static void replyToUser(String... message) { + for (String m : message) { + System.out.println(m); + } + } + + /** + * Display the arraylist to the user. + * + * @param arrayList The arraylist to show to the user. + */ + public static void showList(ArrayList arrayList) { + if (arrayList == null || arrayList.isEmpty()) { + replyToUser(Messages.EMPTY_LIST); + return; + } else { + replyToUser("List: "); + int index = 1; + for (T item : arrayList) { + if (item == null) { + break; + } + String listItem = index + ". " + item; + replyToUser(listItem); + index++; + } + } + } + + /** + * Displays message to the user when an item is sold. + * + * @param item The item sold. + * @param sellQuantity Quantity of the item sold. + * @param remainingQuantity Quantity remained for the item sold. + * @param sellPrice The selling price of the item sold. + */ + public static void showSellMessage(String item, int sellQuantity, int remainingQuantity, float sellPrice) { + float totalValue = sellQuantity * sellPrice; + replyToUser("Quantity of " + item + " sold: " + sellQuantity + ", for: $" + sellPrice + "\n" + + "Quantity remaining: " + remainingQuantity + "\n" + + "Total value sold: " + totalValue + ); + } + + //@@author Fureimi + /** + * Displays a list that contains items of a certain category. + * + * @param arrayList The arraylist to show to the user. + * @param category The category to be listed to the user. + * @param isListMarked Whether the items listed are marked items. + */ + public static void showCustomizedList(ArrayList arrayList, String category, boolean isListMarked) { + // case 1: user wants to list all items of a certain category + if (!category.equals("NA") && !isListMarked) { + int flag = 0; + int counter = 1; + for (Item item : arrayList) { + if (item.getCategory().equals(category)) { + replyToUser(counter + ". Item Index: " + (arrayList.indexOf(item) + 1) + ". " + item); + counter++; + flag = 1; + } + } + if (flag == 0) { + replyToUser("No items were found within the category " + category + "."); + } + // case 2: user wants to list all marked items + } else if (category.equals("NA") && isListMarked) { + int flag = 0; + int counter = 1; + for (Item item : arrayList) { + if (item.isMark) { + replyToUser(counter + ". Item Index: " + (arrayList.indexOf(item) + 1) + ". " + item); + counter++; + flag = 1; + } + } + if (flag == 0) { + replyToUser("There are no marked items in your inventory list!"); + } + // case 3: user wants to list all marked items of a certain category + } else if (!category.equals("NA") && isListMarked) { + int flag = 0; + int counter = 1; + for (Item item : arrayList) { + if (item.isMark && item.getCategory().equals(category)) { + replyToUser(counter + ". Item Index: " + (arrayList.indexOf(item) + 1) + ". " + item); + counter++; + flag = 1; + } + } + if (flag == 0) { + replyToUser("There are no marked items of category '" + category + "' in your inventory list!"); + } + } + } + + public static void showTransactionList(ArrayList transactions) { + int counter = 0; + for (Transaction t: transactions) { + counter++; + replyToUser(counter + ". " + t.toString()); + } + } + + public static void showPromotionList(ArrayList promotionList) { + int counter = 0; + for (Promotion p: promotionList) { + counter++; + replyToUser(counter + ". " + p.toString()); + } + } + + public static void showEditMessage(String item, String editedParameter, String oldParameter, String newParameter) { + switch (editedParameter) { + case "newItemName": + replyToUser("Name of " + item + " from " + oldParameter + " to " + newParameter); + break; + case "newQuantity": + replyToUser("Quantity of " + item + " from " + oldParameter + " to " + newParameter); + break; + case "newUnitOfMeasurement": + replyToUser("Unit of Measurement of " + item + " from " + oldParameter + " to " + newParameter); + break; + case "newCategory": + replyToUser("Category of " + item + " from " + oldParameter + " to " + newParameter); + break; + case "newBuyPrice": + replyToUser("Buy Price of " + item + " from " + oldParameter + " to " + newParameter); + break; + case "newSellPrice": + replyToUser("Sell Price of " + item + " from " + oldParameter + " to " + newParameter); + break; + default: + break; + } + } +} diff --git a/src/test/java/command/AddCommandTest.java b/src/test/java/command/AddCommandTest.java new file mode 100644 index 0000000000..bc51ef9ae7 --- /dev/null +++ b/src/test/java/command/AddCommandTest.java @@ -0,0 +1,69 @@ +package command; + +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddCommandTest { + + + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @Test + public void testExecuteNewItemAddedSuccessfully() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + assertEquals(1, Itemlist.getItems().size()); + } + @Test + public void testExecuteNewItemAddedWithDefaultCategory() { + new AddCommand("TestItem", 10, "pcs", "NA", 5.0f, 10.0f).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00)" + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + assertEquals(1, Itemlist.getItems().size()); + } + + @Test + public void testExecuteExistingItemEditedSuccessfully() { + new AddCommand("TestItem", 10, "pcs", "NA", 5.0f, 10.0f).execute(); + new AddCommand("TestItem", 15, "pcs", "NA", 6.0f, 12.0f).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00)" + System.lineSeparator() + + "Item already exists and item information has been updated" + System.lineSeparator() + + "\n" + + "Edited: " + System.lineSeparator() + + "Quantity of TestItem from 10 to 25" + System.lineSeparator() + + "Buy Price of TestItem from 5.0 to 6.00" + System.lineSeparator() + + "Sell Price of TestItem from 10.0 to 12.00" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + assertEquals(1, Itemlist.getItems().size()); + } + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + +} diff --git a/src/test/java/command/AddPromotionCommandTest.java b/src/test/java/command/AddPromotionCommandTest.java new file mode 100644 index 0000000000..214cd94746 --- /dev/null +++ b/src/test/java/command/AddPromotionCommandTest.java @@ -0,0 +1,297 @@ +package command; + +import common.Messages; +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import promotion.Month; +import promotion.Promotionlist; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static promotion.Promotionlist.isPromoExistNow; + +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + + +public class AddPromotionCommandTest { + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + + @Test + public void addPromotionCommandTest1() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("apple", 1, "fruits", + "NA", 1, 1); + Command promotionTest1 = new AddPromotionCommand("apple", 0.30F, 29, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + addCommandTest1.execute(); + promotionTest1.execute(); + String expectedOutput1 = "added: apple (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + "The following promotion has been added" + System.lineSeparator() + + "apple have a 30.00% discount" + System.lineSeparator() + + "Period: 29 FEB 2024 to 4 APR 2024" + System.lineSeparator() + + "Time: 0000 to 1100" + System.lineSeparator(); + assertEquals(expectedOutput1, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e){ + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void addPromotionCommandTest2() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest2 = new AddCommand("durian", 1, "fruits", + "NA", 1, 1); + Command promotionTest2 = new AddPromotionCommand("durian", 0.30F, 2, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + addCommandTest2.execute(); + promotionTest2.execute(); + String expectedOutput2 = "added: durian (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + "The following promotion has been added" + System.lineSeparator() + + "durian have a 30.00% discount" + System.lineSeparator() + + "Period: 2 APR 2024 to 4 APR 2024" + System.lineSeparator() + + "Time: 0000 to 1100" + System.lineSeparator(); + assertEquals(expectedOutput2, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e){ + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void addPromotionCommandTest3() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest3 = new AddCommand("mango", 1, "fruits", + "NA", 1, 1); + Command promotionTest3 = new AddPromotionCommand("mango", 0.30F, 29, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2026, 0000, 1100); + addCommandTest3.execute(); + promotionTest3.execute(); + String expectedOutput3 = "added: mango (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + "The following promotion has been added" + System.lineSeparator() + + "mango have a 30.00% discount" + System.lineSeparator() + + "Period: 29 FEB 2024 to 4 APR 2026" + System.lineSeparator() + + "Time: 0000 to 1100" + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e){ + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void itemUnavailablePromotionCommandTest() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command promotionTest1 = new AddPromotionCommand("random", 0.30F, 2, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + promotionTest1.execute(); + String expectedOutput3 = + Messages.ITEM_NOT_FOUND + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e){ + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void invalidDiscountPromotionCommandTest() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("banana", 1, "fruits", + "NA", 1, 1); + Command promotionTest1 = new AddPromotionCommand("banana", 100F, 2, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + Command promotionTest2 = new AddPromotionCommand("banana", -1F, 2, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + addCommandTest1.execute(); + promotionTest1.execute(); + promotionTest2.execute(); + String expectedOutput3 = "added: banana (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + Messages.INVALID_DISCOUNT + System.lineSeparator() + + Messages.INVALID_DISCOUNT + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e){ + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void invalidTimePromotionCommandTest() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("lemon", 1, "fruits", + "NA", 1, 1); + Command promotionTest1 = new AddPromotionCommand("lemon", 0.3F, 29, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2024, -1000, 1100); + Command promotionTest2 = new AddPromotionCommand("lemon", 0.3F, 1, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 1200, 1100); + addCommandTest1.execute(); + promotionTest1.execute(); + promotionTest2.execute(); + String expectedOutput3 = "added: lemon (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + Messages.INVALID_TIME + System.lineSeparator() + + Messages.INVALID_TIME + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e) { + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void invalidDatePromotionCommandTest() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("cherry", 1, "fruits", + "NA", 1, 1); + Command promotionTest1 = new AddPromotionCommand("cherry", 0.3F, 30, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + Command promotionTest2 = new AddPromotionCommand("cherry", 0.3F, -1, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + addCommandTest1.execute(); + promotionTest1.execute(); + promotionTest2.execute(); + String expectedOutput3 = "added: cherry (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + Messages.INVALID_DATE + System.lineSeparator() + + Messages.INVALID_DATE + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e) { + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void invalidDurationPromotionCommandTest() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("orange", 1, "fruits", + "NA", 1, 1); + Command promotionTest1 = new AddPromotionCommand("orange", 0.3F, 4, Month.valueOf("APR"), + 2024, 2, Month.valueOf("APR"), 2024, 0000, 1100); + Command promotionTest2 = new AddPromotionCommand("orange", 0.3F, 4, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 2300); + Command promotionTest3 = new AddPromotionCommand("orange", 0.3F, 4, Month.valueOf("DEC"), + 2024, 5, Month.valueOf("APR"), 2024, 0000, 2300); + Command promotionTest4 = new AddPromotionCommand("orange", 0.3F, 4, Month.valueOf("APR"), + 2025, 5, Month.valueOf("APR"), 2024, 0000, 2300); + addCommandTest1.execute(); + promotionTest1.execute(); + promotionTest2.execute(); + promotionTest3.execute(); + promotionTest4.execute(); + String expectedOutput3 = "added: orange (Qty: 1 fruits, Buy: $1.00, Sell: $1.00)" + System.lineSeparator() + + Messages.INVALID_PERIOD + System.lineSeparator() + + Messages.INVALID_PERIOD + System.lineSeparator() + + Messages.INVALID_PERIOD + System.lineSeparator() + + Messages.INVALID_PERIOD + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e) { + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void itemIsOnPromoPromotionCommandTest() { + try { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("apple", 1, "pieces", + "fruits", 1, 1); + Command promotionTest1 = new AddPromotionCommand("apple", 0.3F, 5, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2024, 1100, 1100); + Command promotionTest2 = new AddPromotionCommand("apple", 0.3F, 5, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + addCommandTest1.execute(); + promotionTest1.execute(); + promotionTest2.execute(); + String expectedOutput3 = + "added: apple (Qty: 1 pieces, Buy: $1.00, Sell: $1.00) to fruits" + System.lineSeparator() + + "The following promotion has been added" + System.lineSeparator() + + "apple have a 30.00% discount" + System.lineSeparator() + + "Period: 5 FEB 2024 to 4 APR 2024" + System.lineSeparator() + + "Time: 1100 to 1100" + System.lineSeparator() + + Messages.ITEM_IS_PROMO + System.lineSeparator(); + assertEquals(expectedOutput3, outputStreamCaptor.toString()); + } catch (InvalidDateException | CommandFormatException e) { + System.out.print(""); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + } + + + @Test + void testIsPromoExistNow() { + Command addCommandTest1 = new AddCommand("apple", 1, "pieces", + "fruits", 1, 1); + Command promotionTest1 = new AddPromotionCommand("apple", 0.3F, 5, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2030, 0000, 2359); + Command addCommandTest2 = new AddCommand("banana", 1, "pieces", + "fruits", 1, 1); + Command promotionTest2 = new AddPromotionCommand("banana", 0.3F, 5, Month.valueOf("FEB"), + 2022, 4, Month.valueOf("APR"), 2023, 0000, 2359); + Command addCommandTest3 = new AddCommand("lemon", 1, "pieces", + "fruits", 1, 1); + Command promotionTest3 = new AddPromotionCommand("lemon", 0.3F, 5, Month.valueOf("FEB"), + 2022, 29, Month.valueOf("DEC"), 2024, 0000, 2359); + try { + addCommandTest1.execute(); + addCommandTest2.execute(); + addCommandTest3.execute(); + promotionTest1.execute(); + promotionTest2.execute(); + promotionTest3.execute(); + } catch (CommandFormatException | EmptyListException | InvalidDateException e) { + throw new RuntimeException(e); + } + // Test the method + assertTrue(isPromoExistNow("apple")); + assertTrue(isPromoExistNow("lemon")); + assertFalse(isPromoExistNow("banana")); // Assuming banana is not on promo + } + + + +} diff --git a/src/test/java/command/BestsellerCommandTest.java b/src/test/java/command/BestsellerCommandTest.java new file mode 100644 index 0000000000..776ed3ac3b --- /dev/null +++ b/src/test/java/command/BestsellerCommandTest.java @@ -0,0 +1,67 @@ +package command; + +import common.Messages; +import exceptions.CommandFormatException; +import itemlist.Cashier; +import itemlist.Itemlist; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import exceptions.EmptyListException; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class BestsellerCommandTest { + + private final BestsellerCommand bestsellerCommand = new BestsellerCommand(); + + @BeforeEach + public void reset() { + //clears the transactions list + Cashier.transactions.clear(); + + while (Itemlist.getItem(0) != null) { + Itemlist.deleteItem(0); + } + } + + @Test + public void testExecuteWithEmptyBestseller() throws EmptyListException { + + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + bestsellerCommand.execute(); + String expected = Messages.NO_BESTSELLER + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + } catch (EmptyListException e) { + throw new RuntimeException(e); + } + + } + + @Test + public void testExecuteWithBestseller() throws EmptyListException, CommandFormatException { + //set up an item to test bestseller + AddCommand testAdd = new AddCommand("testItem", 10, "ea","NA", 10.0F, 10.0F); + testAdd.execute(); + SellCommand testSell = new SellCommand("testItem", 10, 0); + testSell.execute(); + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + bestsellerCommand.execute(); + String expectedOutput = "The current best-selling item is testItem" + System.lineSeparator(); + assertEquals(expectedOutput, outputStream.toString()); + } catch (EmptyListException e) { + // It's not supposed to throw EmptyListException in this case + fail("Unexpected EmptyListException thrown"); + } + + } + +} diff --git a/src/test/java/command/CommandTest.java b/src/test/java/command/CommandTest.java new file mode 100644 index 0000000000..e740812c25 --- /dev/null +++ b/src/test/java/command/CommandTest.java @@ -0,0 +1,13 @@ +package command; + +import org.junit.jupiter.api.Test; + +public class CommandTest { + private Command command; + + @Test + public void command_invalidCommand_throwsError() { + + } + +} diff --git a/src/test/java/command/DeleteCommandTest.java b/src/test/java/command/DeleteCommandTest.java new file mode 100644 index 0000000000..19c193d1fe --- /dev/null +++ b/src/test/java/command/DeleteCommandTest.java @@ -0,0 +1,45 @@ +package command; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +public class DeleteCommandTest { + Command addCommandTest1 = new AddCommand("testItem", 1, "EA", + "NA", 1, 10); + Command deleteCommand = new DeleteCommand("testItem"); + + @Test + public void delCommandTest_success() { + try { + addCommandTest1.execute(); + deleteCommand.execute(); + assertFalse(Itemlist.itemIsExist("testItem")); + deleteCommand.execute(); + } catch (CommandFormatException e) { + fail("Unable to delete."); + } catch (InvalidDateException | EmptyListException e) { + throw new RuntimeException(e); + } + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } +} diff --git a/src/test/java/command/DeletePromotionTest.java b/src/test/java/command/DeletePromotionTest.java new file mode 100644 index 0000000000..a2b3b6939e --- /dev/null +++ b/src/test/java/command/DeletePromotionTest.java @@ -0,0 +1,62 @@ +package command; + +import common.Messages; +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import promotion.Month; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DeletePromotionTest { + + @Test + public void deletePromotionTest() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("apple iphone", 100000, "pieces", + "electronics", 100.5F, 400.1F); + Command promotionTest1 = new AddPromotionCommand("apple iphone", 0.30F, 1, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("DEC"), 2024, 0000, 2359); + Command deleteFail = new DeletePromotionCommand("test"); + Command deleteSuccess = new DeletePromotionCommand("apple iphone"); + try { + addCommandTest1.execute(); + promotionTest1.execute(); + deleteFail.execute(); + deleteSuccess.execute(); + String expectedOutput1 = "added: apple iphone (Qty: 100000 pieces, Buy: $100.50, Sell: $400.10) " + + "to electronics" + System.lineSeparator() + + "The following promotion has been added" + System.lineSeparator() + + "apple iphone have a 30.00% discount" + System.lineSeparator() + + "Period: 1 FEB 2024 to 4 DEC 2024" + System.lineSeparator() + + "Time: 0000 to 2359" + System.lineSeparator() + + Messages.ITEM_NOT_ON_PROMO + System.lineSeparator() + + "Promotion for apple iphone has been removed" + System.lineSeparator(); + assertEquals(expectedOutput1, outputStreamCaptor.toString()); + } catch (CommandFormatException | InvalidDateException | EmptyListException e) { + throw new RuntimeException(e); + } + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + +} diff --git a/src/test/java/command/EditCommandTest.java b/src/test/java/command/EditCommandTest.java new file mode 100644 index 0000000000..74a78986fd --- /dev/null +++ b/src/test/java/command/EditCommandTest.java @@ -0,0 +1,146 @@ +package command; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import itemlist.Itemlist; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class EditCommandTest { + + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + + @Test + public void testExecuteEditItemNotFound() { + new EditCommand("NonExistentItem", "NewTestItem", -1, "NA", "NA", -1, -1).execute(); + String expectedOutput = "item not found!" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditItemName() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NewTestItem", -1, "NA", "NA", -1, -1).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Name of TestItem from TestItem to NewTestItem" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditQuantity() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NA", 50, "NA", "NA", -1, -1).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Quantity of TestItem from 10 to 50" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditInvalidQuantity() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NA", -100, "NA", "NA", -1, -1).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Quantity of TestItem from 10 to -100" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditUnitOfMeasurement() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NA", -1, "kg", "NA", -1, -1).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Unit of Measurement of TestItem from pcs to kg" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditCategory() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NA", -1, "NA", "NewTestCategory", -1, -1).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Category of TestItem from TestCategory to NewTestCategory" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditBuyPrice() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NA", -1, "NA", "NA", 6.0f, -1).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Buy Price of TestItem from 5.0 to 6.00" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditSellPrice() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NA", -1, "NA", "NA", -1, 12.0f).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Sell Price of TestItem from 10.0 to 12.00" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + + @Test + public void testExecuteEditMultipleAttributes() { + new AddCommand("TestItem", 10, "pcs", "TestCategory", 5.0f, 10.0f).execute(); + new EditCommand("TestItem", "NewTestItem", 50, "kg", "NewTestCategory", 6.0f, 12.0f).execute(); + String expectedOutput = "added: testitem (Qty: 10 pcs, Buy: $5.00, Sell: $10.00) to TestCategory" + + System.lineSeparator() + "\n" + + "Edited: " + System.lineSeparator() + + "Name of TestItem from TestItem to NewTestItem" + System.lineSeparator() + + "Quantity of TestItem from 10 to 50" + System.lineSeparator() + + "Unit of Measurement of TestItem from pcs to kg" + System.lineSeparator() + + "Category of TestItem from TestCategory to NewTestCategory" + System.lineSeparator() + + "Buy Price of TestItem from 5.0 to 6.00" + System.lineSeparator() + + "Sell Price of TestItem from 10.0 to 12.00" + System.lineSeparator() + + "End of Edits" + System.lineSeparator() + System.lineSeparator(); + assertEquals(expectedOutput, outputStreamCaptor.toString()); + } + +} diff --git a/src/test/java/command/ExitCommandTest.java b/src/test/java/command/ExitCommandTest.java new file mode 100644 index 0000000000..2e2c051341 --- /dev/null +++ b/src/test/java/command/ExitCommandTest.java @@ -0,0 +1,21 @@ +package command; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ExitCommandTest { + @Test + public void testGetIsExit() { + ExitCommand exitCommand = new ExitCommand(true); + assertTrue(ExitCommand.getIsExit()); + } + + @Test + public void testExecute() { + ExitCommand exitCommand = new ExitCommand(false); + exitCommand.execute(); + assertFalse(ExitCommand.getIsExit()); + } +} diff --git a/src/test/java/command/FindCommandTest.java b/src/test/java/command/FindCommandTest.java new file mode 100644 index 0000000000..f01d669c42 --- /dev/null +++ b/src/test/java/command/FindCommandTest.java @@ -0,0 +1,63 @@ +package command; + +import common.Messages; +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class FindCommandTest { + + @Test + public void findCommandTest() throws CommandFormatException, InvalidDateException { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + Command addCommandTest1 = new AddCommand("testItem", 1, "EA", + "NA", 1, 10); + Command findCommand = new FindCommand("item","testItem"); + Command findCommand2 = new FindCommand("NA","failFindCommand"); + Command findCommand3 = new FindCommand("qty/buy/sell/uom/cat","1"); + Command findCommand4 = new FindCommand("buy/sell/uom/cat","1"); + try { + addCommandTest1.execute(); + findCommand.execute(); + findCommand2.execute(); + findCommand3.execute(); + findCommand4.execute(); + String expectedOutput1 = "added: testitem (Qty: 1 EA, Buy: $1.00, Sell: $10.00)" + + System.lineSeparator() + "List: " + System.lineSeparator() + + "1. [ ] testItem (Qty: 1 EA, Buy: $1.00, Sell: $10.00)" + System.lineSeparator() + + Messages.EMPTY_ITEM_LIST + System.lineSeparator() + + "List: " + System.lineSeparator() + + "1. [ ] testItem (Qty: 1 EA, Buy: $1.00, Sell: $10.00)" + System.lineSeparator() + + "List: " + System.lineSeparator() + + "1. [ ] testItem (Qty: 1 EA, Buy: $1.00, Sell: $10.00)" + System.lineSeparator(); + assertEquals(expectedOutput1, outputStreamCaptor.toString()); + } catch (EmptyListException e) { + return; + } + assertFalse(Itemlist.itemIsExist("failFindCommand")); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } +} diff --git a/src/test/java/command/HelpCommandTest.java b/src/test/java/command/HelpCommandTest.java new file mode 100644 index 0000000000..6228927891 --- /dev/null +++ b/src/test/java/command/HelpCommandTest.java @@ -0,0 +1,183 @@ +package command; + +import common.HelpMessages; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HelpCommandTest { + + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @Test + public void testExecuteWithInvalidCommand() { + String command = "invalid_command"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.INVALID_HELP_COMMAND + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithNoCommand() { + String command = "NA"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithBlank() { + String command = " "; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.INVALID_HELP_COMMAND + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithListItemsCommand() { + String command = "list items"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_LIST_ITEMS + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithAddCommand() { + String command = "add"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_ADD + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithSellCommand() { + String command = "sell"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_SELL + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithEditCommand() { + String command = "edit"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_EDIT + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithFindCommand() { + String command = "find"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_FIND + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithMarkCommand() { + String command = "mark"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_MARK + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithUnmarkCommand() { + String command = "unmark"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_UNMARK+ System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithDeleteCommand() { + String command = "delete"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_DEL + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithBestSellerCommand() { + String command = "bestseller"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_BESTSELLER + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithTotalProfitCommand() { + String command = "total profit"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_TOTAL_PROFIT + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithTotalRevenueCommand() { + String command = "total revenue"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_TOTAL_REVENUE + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithPromotionCommand() { + String command = "promotion"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_PROMOTION + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithDeletePromotionCommand() { + String command = "delete promotion"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_DEL_PROMO + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithListPromotionCommand() { + String command = "list promotion"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_LIST_PROMO + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithLowStockCommand() { + String command = "low stock"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_LOW_STOCK + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithListTransactionsCommand() { + String command = "list transactions"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_LIST_TRANSACTIONS + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithExitCommand() { + String command = "exit"; + HelpCommand helpCommand = new HelpCommand(command); + helpCommand.execute(); + assertEquals(HelpMessages.HELP_EXIT + System.lineSeparator(), outputStreamCaptor.toString()); + } + +} + diff --git a/src/test/java/command/IncorrectCommandTest.java b/src/test/java/command/IncorrectCommandTest.java new file mode 100644 index 0000000000..48e4a7a274 --- /dev/null +++ b/src/test/java/command/IncorrectCommandTest.java @@ -0,0 +1,13 @@ +package command; + +import org.junit.jupiter.api.Test; + +public class IncorrectCommandTest { + + private static final IncorrectCommand incorrectCommand = new IncorrectCommand(); + + @Test + public void incorrectCommandTest() { + incorrectCommand.execute(); + } +} diff --git a/src/test/java/command/ListCommandTest.java b/src/test/java/command/ListCommandTest.java new file mode 100644 index 0000000000..6b1108f5e3 --- /dev/null +++ b/src/test/java/command/ListCommandTest.java @@ -0,0 +1,251 @@ +package command; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import item.Item; +import item.Transaction; +import itemlist.Cashier; +import itemlist.Itemlist; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import promotion.Month; +import promotion.Promotion; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListCommandTest extends Cashier { + + //clears all the lists + @BeforeEach + public void reset() { + while (!Promotionlist.getAllPromotion().isEmpty()) { + Promotionlist.deletePromotion(0); + } + while (Itemlist.getItem(0) != null) { + Itemlist.deleteItem(0); + } + while (Cashier.getTransaction(0) != null) { + transactions = new ArrayList<>(); + } + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + + //happy case: has item + @Test + public void listCommandTest_itemList_correct() { + Item test = new Item("testItem", 1, "ea", "NA", 1.00F, 2.00F); + Itemlist.addItem(test); + ListCommand listCommand1 = new ListCommand(Itemlist.getItems(), "NA", false); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "List: " + System.lineSeparator() + "1. " + test + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + + //happy case: has item, filtered correctly + @Test + public void listCommandTest_itemList_correct2() { + Item test = new Item("testItem", 1, "ea", "test", 1.00F, 2.00F); + Itemlist.addItem(test); + ListCommand listCommand1 = new ListCommand(Itemlist.getItems(), "test", false); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "1. Item Index: 1. " + test + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + //happy case: has item, filtered wrongly + @Test + public void listCommandTest_itemList_correct3() { + Item test = new Item("testItem", 1, "ea", "noCat", 1.00F, 2.00F); + Itemlist.addItem(test); + ListCommand listCommand1 = new ListCommand(Itemlist.getItems(), "test", false); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "No items were found within the category test." + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + //happy case: has item, search for marked and is marked + @Test + public void listCommandTest_itemList_correct4() { + Item test = new Item("testItem", 1, "ea", "NA", 1.00F, 2.00F); + Itemlist.addItem(test); + Objects.requireNonNull(Itemlist.getItem(0)).mark(); + ListCommand listCommand1 = new ListCommand(Itemlist.getItems(), "NA", true); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "1. Item Index: 1. " + test + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + //Error: Filter for marked, but no items are marked + @Test + public void listCommandTest_itemList_correct5() { + Item test = new Item("testItem", 1, "ea", "NA", 1.00F, 2.00F); + Itemlist.addItem(test); + ListCommand listCommand1 = new ListCommand(Itemlist.getItems(), "NA", true); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "There are no marked items in your inventory list!" + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + //Error: Filter for marked + cat, have result + @Test + public void listCommandTest_itemList_correct6() { + Item test = new Item("testItem", 1, "ea", "test", 1.00F, 2.00F); + Itemlist.addItem(test); + Objects.requireNonNull(Itemlist.getItem(0)).mark(); + ListCommand listCommand1 = new ListCommand(Itemlist.getItems(), "test", true); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "1. Item Index: 1. " + test + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + //Happy case: has 1 transaction + @Test + public void listCommandTest_transactionList_correct() { + AddCommand addCommand = new AddCommand("testItem", 1, "ea", "NA", 1.00F, 2.00F); + addCommand.execute(); + SellCommand sellCommand = new SellCommand("testItem", 1, 0); + ListCommand listCommand1 = new ListCommand(transactions, "NA"); + try { + sellCommand.execute(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "1. " + transactions.get(0) + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + } catch (CommandFormatException e) { + Assertions.fail("Unexpected CommandFormatException thrown"); + throw new RuntimeException(e); + } + + } + + //happy case: filtered item successfully + @Test + public void listCommandTest_transactionList_correct2() { + AddCommand addCommand = new AddCommand("testItem", 1, "ea", "NA", 1.00F, 2.00F); + addCommand.execute(); + SellCommand sellCommand = new SellCommand("testItem", 1, 0); + try { + sellCommand.execute(); + ListCommand listCommand1 = new ListCommand(transactions, "testItem"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = "1. " + transactions.get(0) + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + } catch (CommandFormatException e) { + Assertions.fail("Unexpected CommandFormatException thrown"); + throw new RuntimeException(e); + } + + } + + @Test + public void listCommandTest_transactionList_correct3() { + AddCommand addCommand = new AddCommand("testItem", 1, "ea", "NA", 1.00F, 2.00F); + addCommand.execute(); + SellCommand sellCommand = new SellCommand("testItem", 1, 0); + ListCommand listCommand1 = new ListCommand(transactions, "failTest"); + try { + sellCommand.execute(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = ""; + assertEquals(expected, outputStream.toString()); + } catch (CommandFormatException e) { + Assertions.fail("Unexpected CommandFormatException thrown"); + throw new RuntimeException(e); + } + + } + + @Test + public void listCommandTest_promotionList_correct() throws InvalidDateException, CommandFormatException { + AddCommand addCommand = new AddCommand("testItem", 1, "ea", "NA", 1.00F, 2.00F); + addCommand.execute(); + Command testPromo = new AddPromotionCommand("testItem", 0.30F, 2, Month.valueOf("APR"), + 2024, 4, Month.valueOf("APR"), 2024, 0000, 1100); + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + testPromo.execute(); + String expected = "The following promotion has been added" + System.lineSeparator() + + "testItem have a 30.00% discount" + System.lineSeparator() + + "Period: 2 APR 2024 to 4 APR 2024" + System.lineSeparator() + + "Time: 0000 to 1100" + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + } catch (EmptyListException e) { + Assertions.fail("Unexpected EmptyListException thrown."); + throw new RuntimeException(e); + } catch (CommandFormatException | InvalidDateException e) { + Assertions.fail("Unexpected exception thrown"); + throw new RuntimeException(e); + } + + } + @Test + public void listCommandTest_itemList_empty() { + ListCommand listCommand1 = new ListCommand(new ArrayList(), "NA", false); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = ""; + assertEquals(expected, outputStream.toString()); + + } + + @Test + public void listCommandTest_transactionList_empty() { + ListCommand listCommand1 = new ListCommand(new ArrayList(), "NA"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = ""; + assertEquals(expected, outputStream.toString()); + + } + + @Test + public void listCommandTest_promotionList_empty() { + ListCommand listCommand1 = new ListCommand(new ArrayList()); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + listCommand1.execute(); + String expected = ""; + assertEquals(expected, outputStream.toString()); + + } +} diff --git a/src/test/java/command/LowStockCommandTest.java b/src/test/java/command/LowStockCommandTest.java new file mode 100644 index 0000000000..08d8ecd901 --- /dev/null +++ b/src/test/java/command/LowStockCommandTest.java @@ -0,0 +1,85 @@ +package command; + +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class LowStockCommandTest { + + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + + @Test + public void testExecuteNoItemsLowOnStock() { + new LowStockCommand(5).execute(); + assertEquals("Out-of-stock Items:" + System.lineSeparator() + + "No items out of stock" + System.lineSeparator() + + "Low-on-stock Items: (less than 5)" + System.lineSeparator() + + "No items low on stock" + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithItemsLowOnStock() { + new AddCommand("Item1", 3, "pcs", "testCat", 1, 2).execute(); + new AddCommand("Item2", 1, "pcs", "testCat", 1, 2).execute(); + new LowStockCommand(5).execute(); + assertEquals("added: item1 (Qty: 3 pcs, Buy: $1.00, Sell: $2.00) to testCat" + System.lineSeparator() + + "added: item2 (Qty: 1 pcs, Buy: $1.00, Sell: $2.00) to testCat" + System.lineSeparator() + + "Out-of-stock Items:" + System.lineSeparator() + + "No items out of stock" + System.lineSeparator() + + "Low-on-stock Items: (less than 5)" + System.lineSeparator() + + "Item1" + System.lineSeparator() + + "Item2" + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithNoItemsOutOfStock() { + new AddCommand("Item1", 100, "pcs", "testCat", 1, 2).execute(); + new AddCommand("Item2", 50, "pcs", "testCat", 1, 2).execute(); + new LowStockCommand(5).execute(); + assertEquals("added: item1 (Qty: 100 pcs, Buy: $1.00, Sell: $2.00) to testCat" + System.lineSeparator() + + "added: item2 (Qty: 50 pcs, Buy: $1.00, Sell: $2.00) to testCat" + System.lineSeparator() + + "Out-of-stock Items:" + System.lineSeparator() + + "No items out of stock" + System.lineSeparator() + + "Low-on-stock Items: (less than 5)" + System.lineSeparator() + + "No items low on stock" + System.lineSeparator(), outputStreamCaptor.toString()); + } + + @Test + public void testExecuteWithItemsOutOfStockAndLowOnStock() { + new AddCommand("Item1", 3, "pcs", "testCat", 1, 2).execute(); + new AddCommand("Item2", 10, "pcs", "testCat", 1, 2).execute(); + new LowStockCommand(5).execute(); + assertEquals("added: item1 (Qty: 3 pcs, Buy: $1.00, Sell: $2.00) to testCat" + System.lineSeparator() + + "added: item2 (Qty: 10 pcs, Buy: $1.00, Sell: $2.00) to testCat" + System.lineSeparator() + + "Out-of-stock Items:" + System.lineSeparator() + + "No items out of stock" + System.lineSeparator() + + "Low-on-stock Items: (less than 5)" + System.lineSeparator() + + "Item1" + System.lineSeparator(), outputStreamCaptor.toString()); + } +} diff --git a/src/test/java/command/SellCommandTest.java b/src/test/java/command/SellCommandTest.java new file mode 100644 index 0000000000..7219f2badb --- /dev/null +++ b/src/test/java/command/SellCommandTest.java @@ -0,0 +1,74 @@ +package command; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class SellCommandTest { + + @Test + public void testExecuteItemNotFound() { + SellCommand sellCommand = new SellCommand("Nonexistent Item", 5, 0.1f); + try { + sellCommand.execute(); + } catch (CommandFormatException e) { + assertEquals("Item not found!", e.getMessage()); + } + } + + @Test + public void testExecuteInsufficientStock() { + SellCommand sellCommand = new SellCommand("Low Stock Item", 10, 0.1f); + try { + sellCommand.execute(); + } catch (CommandFormatException e) { + assertEquals("There is insufficient stock!", e.getMessage()); + } + } + + @Test + public void testExecuteSuccessfulSale() { + SellCommand sellCommand = new SellCommand("Available Item", 3, 0.1f); + try { + sellCommand.execute(); + } catch (CommandFormatException e) { + fail("No exception should be thrown for a successful sale."); + } + } + + + @Test + public void sellItemTest() { + try { + Command sellCommandTest1 = new SellCommand("testItem", 1, 3); + Command sellCommandTest2 = new SellCommand("testItem", 7, 14); + sellCommandTest1.execute(); + sellCommandTest2.execute(); + } catch (CommandFormatException e) { + fail("Unable to sell item."); + } catch (InvalidDateException | EmptyListException e) { + throw new RuntimeException(e); + } + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + +} diff --git a/src/test/java/command/TotalProfitCommandTest.java b/src/test/java/command/TotalProfitCommandTest.java new file mode 100644 index 0000000000..78a17d37bf --- /dev/null +++ b/src/test/java/command/TotalProfitCommandTest.java @@ -0,0 +1,81 @@ +package command; + +import common.Messages; +import exceptions.CommandFormatException; +import itemlist.Cashier; +import itemlist.Itemlist; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import parser.CommandType; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TotalProfitCommandTest { + + @BeforeEach + public void reset() { + //clears the transactions list + Cashier.transactions.clear(); + + while (Itemlist.getItem(0) != null) { + Itemlist.deleteItem(0); + } + } + + @Test + public void testProfitWithNoItems() { + TotalProfitCommand totalProfitCommand = new TotalProfitCommand(CommandType.TOTAL_PROFIT); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + totalProfitCommand.execute(); + String expected = Messages.NO_BESTSELLER + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + @Test + public void testProfitWithItems() throws CommandFormatException { + //set up an item to test bestseller + AddCommand testAdd = new AddCommand("testItem", 10, "ea","NA", 10.0F, 20.0F); + testAdd.execute(); + SellCommand testSell = new SellCommand("testItem", 10, 0); + testSell.execute(); + TotalProfitCommand totalProfitCommand = new TotalProfitCommand(CommandType.TOTAL_PROFIT); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + totalProfitCommand.execute(); + String expected = "You have earned 100.0 in profits so far." + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + @Test + public void testRevenueWithNoItems() { + TotalProfitCommand totalProfitCommand = new TotalProfitCommand(CommandType.TOTAL_REVENUE); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + totalProfitCommand.execute(); + String expected = Messages.NO_BESTSELLER + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } + + @Test + public void testRevenueWithItems() throws CommandFormatException { + //set up an item to test bestseller + AddCommand testAdd = new AddCommand("testItem", 10, "ea","NA", 10.0F, 20.0F); + testAdd.execute(); + SellCommand testSell = new SellCommand("testItem", 10, 0); + testSell.execute(); + TotalProfitCommand totalProfitCommand = new TotalProfitCommand(CommandType.TOTAL_REVENUE); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + totalProfitCommand.execute(); + String expected = "You have earned 200.0 in revenue so far." + System.lineSeparator(); + assertEquals(expected, outputStream.toString()); + + } +} diff --git a/src/test/java/item/ItemTest.java b/src/test/java/item/ItemTest.java new file mode 100644 index 0000000000..bd718cae5d --- /dev/null +++ b/src/test/java/item/ItemTest.java @@ -0,0 +1,84 @@ +package item; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ItemTest { + + private Item item; + + @BeforeEach + void setUp() { + // Initialize Item object before each test + item = new Item("Apple", 10, "Kg", "Fruit", 0.50f, 1.00f); + } + + @Test + void testItemCreation() { + assertNotNull(item, "Item should not be null after creation"); + assertEquals("Apple", item.getItemName(), "Check item name"); + assertEquals(10, item.getQuantity(), "Check quantity"); + assertEquals("Kg", item.getUnitOfMeasurement(), "Check unit of measurement"); + assertEquals("Fruit", item.getCategory(), "Check category"); + assertEquals(0.50f, item.getBuyPrice(), "Check buy price"); + assertEquals(1.00f, item.getSellPrice(), "Check sell price"); + assertFalse(item.getIsOOS(), "Item should not be out of stock"); + } + + @Test + void testSetItemName() { + item.setItemName("Banana"); + assertEquals("Banana", item.getItemName(), "Item name should be updated to Banana"); + } + + @Test + void testSetQuantity() { + item.setQuantity(20); + assertEquals(20, item.getQuantity(), "Quantity should be updated to 20"); + assertFalse(item.getIsOOS(), "Item should not be marked out of stock with quantity 20"); + + // Testing out of stock scenario + item.setQuantity(0); + assertEquals(0, item.getQuantity(), "Quantity should be updated to 0"); + assertTrue(item.getIsOOS(), "Item should be marked out of stock"); + } + + @Test + void testSetUnitOfMeasurement() { + item.setUnitOfMeasurement("Boxes"); + assertEquals("Boxes", item.getUnitOfMeasurement(), "Unit of measurement should be updated to Boxes"); + } + + @Test + void testSetCategory() { + item.setCategory("Snacks"); + assertEquals("Snacks", item.getCategory(), "Category should be updated to Snacks"); + } + + @Test + void testSetBuyPrice() { + item.setBuyPrice(0.75f); + assertEquals(0.75f, item.getBuyPrice(), "Buy price should be updated to 0.75"); + } + + @Test + void testSetSellPrice() { + item.setSellPrice(1.50f); + assertEquals(1.50f, item.getSellPrice(), "Sell price should be updated to 1.50"); + } + + @Test + void testMarkUnmarkItem() { + item.mark(); + assertTrue(item.getMarkStatus(), "Item should be marked"); + + item.unmark(); + assertFalse(item.getMarkStatus(), "Item should be unmarked"); + } +} + diff --git a/src/test/java/itemlist/CashierTest.java b/src/test/java/itemlist/CashierTest.java new file mode 100644 index 0000000000..74d37efb21 --- /dev/null +++ b/src/test/java/itemlist/CashierTest.java @@ -0,0 +1,32 @@ +package itemlist; + +import item.Item; +import item.Transaction; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +public class CashierTest { + + @Test + public void addTransactionTest() { + Item testItem = new Item("testItem", 1, "ea", "NA", 1.00F, 2.00F); + Transaction testTransaction = new Transaction("testTransaction", 1, 1.00F, 2.00F); + Cashier.addItem(testTransaction); + assert(Cashier.getTransactions().contains(testTransaction)); + Cashier.deleteItem(Cashier.getTransactions().size() - 1); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } +} diff --git a/src/test/java/itemlist/ItemlistTest.java b/src/test/java/itemlist/ItemlistTest.java new file mode 100644 index 0000000000..eb3d496174 --- /dev/null +++ b/src/test/java/itemlist/ItemlistTest.java @@ -0,0 +1,80 @@ +package itemlist; + +import item.Item; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class ItemlistTest { + + private Item testItem; + + @BeforeEach + void setUp() { + // This will be run before each test, setting up test conditions + float buyPrice = 0.5F; + float sellPrice = 1.2F; + testItem = new Item("TestItem", 10, "test UOM", "test Cat", buyPrice, + sellPrice); + Itemlist.addItem(testItem); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + } + + @Test + void getSizeTest() { + assertEquals(1, Itemlist.getItems().size()); + } + + @Test + void deleteItem_shouldDecreaseListSize() { + Itemlist.deleteItem(0); + assertEquals(0, Itemlist.getItems().size()); + } + + @Test + void editQuantity_shouldUpdateQuantity() { + Itemlist.editQuantity(0, 5); + assertEquals(5, testItem.getQuantity()); + assertFalse(testItem.getIsOOS()); + } + + @Test + void editQuantity_shouldMarkOutOfStock() { + Itemlist.editQuantity(0, 0); + assertTrue(testItem.getIsOOS()); + } + + @Test + void itemIsExist_shouldReturnTrueForExistingItem() { + assertTrue(Itemlist.itemIsExist("TestItem")); + } + + @Test + void itemIsExist_shouldReturnFalseForNonExistingItem() { + assertFalse(Itemlist.itemIsExist("NonExistingItem")); + } + + @Test + void getItem_shouldReturnCorrectItem() { + Item item = Itemlist.getItem(0); + assert item != null; + assertEquals("TestItem", item.getItemName()); + assertEquals(10, item.getQuantity()); + } + + @Test + void getIndex_shouldReturnCorrectIndex() { + int index = Itemlist.getIndex(testItem); + assertEquals(0, index); + } +} diff --git a/src/test/java/parser/ParserTest.java b/src/test/java/parser/ParserTest.java new file mode 100644 index 0000000000..e342e602d1 --- /dev/null +++ b/src/test/java/parser/ParserTest.java @@ -0,0 +1,790 @@ +package parser; + +import command.AddCommand; +import command.AddPromotionCommand; +import command.BestsellerCommand; +import command.Command; +import command.DeleteCommand; +import command.DeletePromotionCommand; +import command.EditCommand; +import command.ExitCommand; +import command.FindCommand; +import command.IncorrectCommand; +import command.ListCommand; +import command.LowStockCommand; +import command.MarkCommand; +import command.SellCommand; +import command.TotalProfitCommand; +import command.UnmarkCommand; +import common.HelpMessages; +import common.Messages; +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import promotion.Month; +import promotion.Promotionlist; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ParserTest { + + private Parser parser; + + @BeforeEach + public void setUp() { + parser = new Parser(); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } + + @Test + public void testParseCommandWithBlankText() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = " "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_COMMAND + System.lineSeparator() + + HelpMessages.HELP + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + @Test + public void testParseAddCommandWithBlankText() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_ADD_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithValidItem() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/1 /pc cat/test buy/1 sell/2"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithBlankItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_ADD_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithInvalidQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/a /pc cat/test buy/1 sell/2"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_ADD_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithLargeQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/9999999999999999999 /pc cat/test buy/1 sell/2"; + parser.parseInput(userInput); + String expectedMessage = Messages.QTY_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithBlankCategory() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/1 /pc cat/ buy/1 sell/2"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_ADD_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithLargeBuy() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/1 /pc cat/test buy/999999999999999999999999999 sell/2"; + parser.parseInput(userInput); + String expectedMessage = Messages.BUY_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithNegativeBuy() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/1 /pc cat/test buy/-1 sell/2"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_ADD_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithLargeSell() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/1 /pc cat/test buy/1 sell/99999999999999999999999999"; + parser.parseInput(userInput); + String expectedMessage = Messages.SELL_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommandWithNegativeSell() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "add itemname qty/1 /pc cat/test buy/1 sell/-2"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_ADD_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseHelpCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "help"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseHelpCommandWithBlankCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "help c/ "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_HELP_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseHelpCommandWithInvalidCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "help c/invalid "; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseHelpCommandWithValidCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "help c/add"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseDeleteCommandWithBlankItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "del "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_DELETE_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseDeleteCommandWithItemThatDoesNotExist() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "del non existent item"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseDeleteCommandWithValidItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "del validItemName"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseSellCommandWithBlankItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "sell "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_SELL_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseSellCommandWithItemThatDoesNotExist() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "sell non existent item qty/1"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseSellCommandWithValidItemNameAndInvalidQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "sell validItemName qty/a"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_SELL_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseSellCommandWithValidItemNameAndLargeQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "sell validItemName qty/9999999999999999999"; + parser.parseInput(userInput); + String expectedMessage = Messages.QTY_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithBlankItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_EDIT_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithValidItemNameAndValidQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName qty/10"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithNegativeQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName qty/-1"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_EDIT_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithLargeQuantity() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName qty/99999999999999999"; + parser.parseInput(userInput); + String expectedMessage = Messages.QTY_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithValidUOM() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName uom/kg"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithInvalidUOM() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName uom/1"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithBlankCat() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName cat/ "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_EDIT_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithInvalidCat() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName cat/1"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithNegativeBuy() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName buy/-1"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_EDIT_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithLargeBuy() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName buy/99999999999999999"; + parser.parseInput(userInput); + String expectedMessage = Messages.BUY_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithNegativeSell() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName sell/-1"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_EDIT_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseEditCommandWithLargeSell() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "edit validItemName sell/99999999999999999"; + parser.parseInput(userInput); + String expectedMessage = Messages.SELL_TOO_LARGE + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseFindCommandWithBlankInfo() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "find "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_FIND_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseFindCommandWithBlankKeyword() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "find /info "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_FIND_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParsePromotionCommandWithInvalidDiscount() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "promotion apple discount/invalid period /from 2 Apr 2024 /to " + + "3 Apr 2024 time /from 0000 /to 2359"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_PROMOTION_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParsePromotionCommandWithInvalidPeriodFormat() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "promotion apple discount/10 period /invalidformat"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_PROMOTION_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseMarkCommandWithValidItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "mark validItemName"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseMarkCommandWithBlankItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "mark "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_MARK_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseMarkCommandWithInvalidItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "mark invalidItemName"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseUnmarkCommandWithValidItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "unmark validItemName"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseUnmarkCommandWithBlankItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "unmark "; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_UNMARK_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseUnmarkCommandWithInvalidItemName() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "unmark invalidItemName"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseListItemsCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "list_items"; + parser.parseInput(userInput); + String expectedMessage = Messages.EMPTY_ITEM_LIST + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseListPromotionsCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "list_promotions"; + parser.parseInput(userInput); + String expectedMessage = Messages.EMPTY_PROMOTION_LIST + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseTotalProfitCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "total_profit"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseTotalRevenueCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "total_revenue"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseBestsellerCommand() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "bestseller"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseLowStockCommandWithValidAmount() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "low_stock /10"; + parser.parseInput(userInput); + String expectedMessage = ""; + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + @Test + public void testParseLowStockCommandWithBlankAmount() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "low_stock /"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_LOW_STOCK_FORMAT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseLowStockCommandWithLargeAmount() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "low_stock /99999999999999999999"; + parser.parseInput(userInput); + String expectedMessage = Messages.INVALID_LOW_STOCK_AMOUNT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseLowStockCommandWithNegativeAmount() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + String userInput = "low_stock /-1"; + parser.parseInput(userInput); + String expectedMessage = Messages.NEGATIVE_LOW_STOCK_AMOUNT + System.lineSeparator(); + assertEquals(expectedMessage, outputStreamCaptor.toString()); + } + + @Test + public void testParseAddCommand() { + String userInput = "add ItemName qty/10 /unitOfMeasurement cat/Category buy/10.5 sell/15.0"; + Command command = parser.parseInput(userInput); + assertInstanceOf(AddCommand.class, command); + AddCommand addCommand = (AddCommand) command; + assertEquals("itemname", addCommand.getItemName()); + assertEquals(10, addCommand.getQuantity()); + assertEquals("unitOfMeasurement", addCommand.getUnitOfMeasurement()); + assertEquals("Category", addCommand.getCategory()); + assertEquals(10.5f, addCommand.getBuyPrice(), 0.01); + assertEquals(15.0f, addCommand.getSellPrice(), 0.01); + } + + @Test + public void testParseEditCommand() { + String userInput = "edit ItemName name/NewName qty/20 uom/NewUOM cat/NewCategory buy/15.0 sell/20.0"; + Command command = parser.parseInput(userInput); + assertInstanceOf(EditCommand.class, command); + EditCommand editCommand = (EditCommand) command; + assertEquals("itemname", editCommand.getItemName()); + assertEquals("newname", editCommand.getNewItemName()); + assertEquals(20, editCommand.getNewQuantity()); + assertEquals("NewUOM", editCommand.getNewUnitOfMeasurement()); + assertEquals("NewCategory", editCommand.getNewCategory()); + assertEquals(15.0f, editCommand.getNewBuyPrice(), 0.01); + assertEquals(20.0f, editCommand.getNewSellPrice(), 0.01); + } + + @Test + public void testParseSellCommand1() { + String userInput = "sell ItemName qty/5"; + Command command = parser.parseInput(userInput); + assertInstanceOf(SellCommand.class, command); + SellCommand sellCommand = (SellCommand) command; + assertEquals("itemname", sellCommand.getItemName()); + assertEquals(5, sellCommand.getSellQuantity()); + } + + @Test + public void testParseSellCommand2() { + Command addCommandTest1 = new AddCommand("lemon", 5, "fruits", + "NA", 1, 1); + Command promotionTest1 = new AddPromotionCommand("lemon", 0.3F, 2, Month.valueOf("FEB"), + 2024, 4, Month.valueOf("APR"), 2025, 0000, 2359); + try { + promotionTest1.execute(); + addCommandTest1.execute(); + } catch (CommandFormatException | InvalidDateException | EmptyListException e) { + throw new RuntimeException(e); + } + String userInput1 = "sell lemon qty/1"; + Command command1 = parser.parseInput(userInput1); + assertInstanceOf(SellCommand.class, command1); + SellCommand sellCommand = (SellCommand) command1; + assertEquals("lemon", sellCommand.getItemName()); + assertEquals(1, sellCommand.getSellQuantity()); + } + + @Test + public void testParseFindCommand() { + String userInput = "find /info Keyword"; + Command command = parser.parseInput(userInput); + assertInstanceOf(FindCommand.class, command); + FindCommand findCommand = (FindCommand) command; + assertEquals("info", findCommand.getItemInfo()); + assertEquals("keyword", findCommand.getKeyword()); + } + + @Test + public void testPrepareFindCommand() { + // Test valid find command + String userInput = "find /item Keyword"; + Command command = parser.parseInput(userInput); + assertTrue(command instanceof FindCommand); + FindCommand findCommand = (FindCommand) command; + assertEquals("item", findCommand.getItemInfo()); + assertEquals("keyword", findCommand.getKeyword()); + + // Test find command without specifying item info + userInput = "find Keyword"; + command = parser.parseInput(userInput); + assertTrue(command instanceof FindCommand); + findCommand = (FindCommand) command; + assertEquals("NA", findCommand.getItemInfo()); + assertEquals("keyword", findCommand.getKeyword()); + } + + @Test + public void testParseListCommand() { + String userInput = "list_items cat/Category"; + Command command = parser.parseInput(userInput); + assertInstanceOf(ListCommand.class, command); + ListCommand listCommand = (ListCommand) command; + assertEquals("category", listCommand.getCategory()); + } + + @Test + public void testParseMarkCommand() { + String userInput = "mark ItemName"; + Command command = parser.parseInput(userInput); + assertInstanceOf(MarkCommand.class, command); + MarkCommand markCommand = (MarkCommand) command; + assertEquals("itemname", markCommand.getItemName()); + } + + @Test + public void testParseUnmarkCommand() { + String userInput = "unmark ItemName"; + Command command = parser.parseInput(userInput); + assertInstanceOf(UnmarkCommand.class, command); + UnmarkCommand unmarkCommand = (UnmarkCommand) command; + assertEquals("itemname", unmarkCommand.getItemName()); + } + + @Test + public void testParsePromotionCommand() { + String userInput = "promotion apple discount/10 period /from 2 Apr 2024 /to " + + "3 Apr 2024 time /from 0000 /to 2359"; + Command command = parser.parseInput(userInput); + assertInstanceOf(AddPromotionCommand.class, command); + } + + @Test + public void testPrepareDeleteCommand() { + Command addCommand = new AddCommand("lemon", 5, "fruits", + "NA", 1, 1); + Command delCommand1 = new DeleteCommand("lemon"); + Command delCommand2 = new DeleteCommand(" "); + try { + addCommand.execute(); + delCommand1.execute(); + delCommand2.execute(); + } catch (CommandFormatException | InvalidDateException | EmptyListException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testLowStockCommand() { + // Test valid low stock command + String userInput1 = "low_stock /10"; + Command command1 = parser.parseInput(userInput1); + assertTrue(command1 instanceof LowStockCommand); + LowStockCommand lowStockCommand1 = (LowStockCommand) command1; + assertEquals(10, lowStockCommand1.getAmount()); + } + + @Test + public void testParseExitCommand() { + String userInput = "exit"; + Command command = parser.parseInput(userInput); + assertInstanceOf(ExitCommand.class, command); + } + + @Test + public void testParseIncorrectCommand() { + String userInput = "incorrect"; + Command command = parser.parseInput(userInput); + assertInstanceOf(IncorrectCommand.class, command); + } + + @Test + public void testParseTotalProfitCommand2() { + String userInput = "total_profit"; + Command command = parser.parseInput(userInput); + assertInstanceOf(TotalProfitCommand.class, command); + } + + @Test + public void testParseBestsellerCommand2() { + String userInput = "bestseller"; + Command command = parser.parseInput(userInput); + assertInstanceOf(BestsellerCommand.class, command); + } + + @Test + public void testParseDeletePromotionCommand() { + String userInput = "del_promo ItemName"; + Command command = parser.parseInput(userInput); + assertInstanceOf(DeletePromotionCommand.class, command); + } + + @Test + public void testParseUnrecognizedCommand() { + String userInput = "unknown command"; + Command command = parser.parseInput(userInput); + assertInstanceOf(IncorrectCommand.class, command); + } + + @Test + public void testPrepareDeletePromo() { + String userInput = "del_promo ItemName"; + Command command = parser.parseInput(userInput); + assertInstanceOf(DeletePromotionCommand.class, command); + DeletePromotionCommand deletePromotionCommand = (DeletePromotionCommand) command; + assertEquals("itemname", deletePromotionCommand.getItemName()); + } +} + + diff --git a/src/test/java/promotion/PromotionTest.java b/src/test/java/promotion/PromotionTest.java new file mode 100644 index 0000000000..2c4fdf0caa --- /dev/null +++ b/src/test/java/promotion/PromotionTest.java @@ -0,0 +1,35 @@ +package promotion; + +import itemlist.Itemlist; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import storage.PromotionStorage; +import storage.Storage; +import storage.TransactionLogs; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class PromotionTest { + + @Test + public void testToString() { + Promotion promotion = new Promotion("item", 0.1f, 1, Month.JAN, + 2022, 10, Month.FEB, 2023, 800, 1600); + + String expected = "item have a 10.00% discount\n" + + "Period: 1 JAN 2022 to 10 FEB 2023\n" + + "Time: 0800 to 1600"; + assertEquals(expected, promotion.toString()); + } + + @AfterEach + void tearDown() { + // This will be run after each test, cleaning up + Itemlist.getItems().clear(); // clear the list for next test + Promotionlist.getAllPromotion().clear(); + Storage.updateFile("", false); + PromotionStorage.updateFile("", false); + TransactionLogs.updateFile("", false); + } +} + 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/duke/StockMasterTest.java b/src/test/java/seedu/duke/StockMasterTest.java new file mode 100644 index 0000000000..1195625794 --- /dev/null +++ b/src/test/java/seedu/duke/StockMasterTest.java @@ -0,0 +1,17 @@ +package seedu.duke; + +import exceptions.CommandFormatException; +import exceptions.EmptyListException; +import exceptions.InvalidDateException; +import org.junit.jupiter.api.Test; + + +import java.io.IOException; + +public class StockMasterTest { + + @Test + public void testStockMaster() throws CommandFormatException, InvalidDateException, IOException, EmptyListException { + StockMaster stockMaster = new StockMaster(); + } +} diff --git a/src/test/java/storage/PromotionStorageTest.java b/src/test/java/storage/PromotionStorageTest.java new file mode 100644 index 0000000000..c113164b26 --- /dev/null +++ b/src/test/java/storage/PromotionStorageTest.java @@ -0,0 +1,32 @@ +package storage; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class PromotionStorageTest { + @Test + public void readFromFileTest() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + String directory = "./testFile.txt"; + File testFile = new File(directory); + String aPromotion = "testItem have a 30.00% discount\n" + + "Period: 15 MAR 2024 to 16 MAY 2024\n" + + "Time: 1200 to 2359"; + try { + PromotionStorage.writeToFile(directory, aPromotion, true); + PromotionStorage.readFromFile(directory); + } catch (IOException e) { + fail("File not found"); + } + assert testFile.delete(); + assertNotEquals("Read Promotion File Error", outputStream.toString()); + } +} diff --git a/src/test/java/storage/StorageTest.java b/src/test/java/storage/StorageTest.java new file mode 100644 index 0000000000..912d94ba84 --- /dev/null +++ b/src/test/java/storage/StorageTest.java @@ -0,0 +1,78 @@ +package storage; + +import item.Item; +import itemlist.Itemlist; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static storage.Storage.interpretLines; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Scanner; + +public class StorageTest { + @Test + public void readFromFile_fileNotFound() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + String directory = "./testFile1.txt"; + File testFile = new File(directory); + try { + Storage.writeToFile(directory, "", true); + testFile.delete(); + Storage.readFromFile(directory); + assertEquals("File does not exist. Creating a new Text File" + + System.lineSeparator(), outputStream.toString()); + } catch (IOException e) { + fail("failed to create a file."); + } + } + + @Test + public void writeToFile_aLine_writeSuccessful() { + String directory = "./testFile2.txt"; + File testFile = new File(directory); + String aLine = "A line"; + try { + Storage.writeToFile(directory, aLine, true); + Scanner scanner = new Scanner(testFile); + String lineSkipped = scanner.nextLine(); + scanner.close(); + testFile.delete(); + assertEquals(aLine, lineSkipped); + } catch (IOException e) { + fail("File not found"); + } + } + + @Test + public void interpretLinesTest() { + String directory = "./testFile3.txt"; + File testFile = new File(directory); + String aLine = "1. | [ ] | testItem | Qty: 1 unit | Cat: interpretLines | BuyPrice: $1.00 | SellPrice: $1.00"; + try { + Storage.writeToFile(directory, aLine, true); + Scanner scanner = new Scanner(testFile); + interpretLines(scanner); + scanner.close(); + } catch (IOException e) { + fail("File not found"); + } catch (NumberFormatException e) { + fail("Incorrect number format"); + } + assert testFile.delete(); + Item itemAdded = Itemlist.getItem(Itemlist.getItems().size() - 1); + Itemlist.deleteItem(Itemlist.getIndex(Itemlist.getItem("testItem"))); + assertEquals("testItem", itemAdded.getItemName()); + } + + @Test + public void getFileDirectory_correct() { + assertEquals(Storage.getFileDirectory(), "./StockMasterData.txt"); + } +} diff --git a/src/test/java/storage/TransactionLogsTest.java b/src/test/java/storage/TransactionLogsTest.java new file mode 100644 index 0000000000..2624e88d53 --- /dev/null +++ b/src/test/java/storage/TransactionLogsTest.java @@ -0,0 +1,41 @@ +package storage; + +import item.Transaction; +import itemlist.Cashier; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class TransactionLogsTest { + @Test + public void interpretLinesTest() { + String directory = "./testFile.txt"; + File testFile = new File(directory); + String aTransaction = "Date: 2024-04-14 15:25:47\n" + + "Transaction ID: 1\n" + + "Item Name: testItem\n" + + "Quantity: 10\n" + + "Unit Price: 20.0\n" + + "Total Price: 200.0\n" + + "Profit: 100.0"; + try { + TransactionLogs.writeToFile(directory, aTransaction, true); + Scanner scanner = new Scanner(testFile); + TransactionLogs.interpretLines(scanner); + scanner.close(); + } catch (IOException e) { + fail("File not found"); + } catch (NumberFormatException e) { + fail("Incorrect number format"); + } + assert testFile.delete(); + Transaction transactionAdded = Cashier.getTransaction(Cashier.getTransactions().size() - 1); + Cashier.deleteItem(Cashier.getTransactions().size() - 1); + assertEquals(200.0, transactionAdded.getTotalPrice()); + } +} diff --git a/src/test/java/ui/TextUiTest.java b/src/test/java/ui/TextUiTest.java new file mode 100644 index 0000000000..0b21425e76 --- /dev/null +++ b/src/test/java/ui/TextUiTest.java @@ -0,0 +1,65 @@ +package ui; + +import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; + +public class TextUiTest { + @Test + public void testReplyToUser() { + String message = "Message 1 to display"; + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + TextUi.replyToUser(message); + assert outputStreamCaptor.toString().contains((message)); + } + + @Test + public void testValidInput() { + provideInput("add Item"); + assertEquals("add Item", TextUi.getUserInput()); + + provideInput(" "); + assertEquals("Invalid Command", TextUi.getUserInput()); + } + + @Test + public void testShowInventoryList() { + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStreamCaptor)); + ArrayList itemList = new ArrayList<>(); + itemList.add("test 1"); + itemList.add("test 2"); + TextUi.showList(itemList); + String[] output = outputStreamCaptor.toString().split("\\r?\\n"); + String line1 = "List: "; + String line2 = "1. test 1"; + String line3 = "2. test 2"; + List expectedOutput = Arrays.asList(line1, line2, line3); + assertLinesMatch(expectedOutput, Arrays.asList(output)); + } + + @Test + public void testShowEmptyInventoryList() { //only test for empty arrayList as parser will trim away empty inputs + ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outputStreamCaptor)); + ArrayList itemList = new ArrayList<>(); //empty arrayList + TextUi.showList(itemList); + String[] output = outputStreamCaptor.toString().split("\\r?\\n"); + String line = "There is nothing here! Time to spend some money and stock em up!"; + List expectedOutput = List.of(line); + assertLinesMatch(expectedOutput, Arrays.asList(output)); + } + + void provideInput(String input) { + ByteArrayInputStream testIn = new ByteArrayInputStream(input.getBytes()); + System.setIn(testIn); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..d9cb163dd4 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,18 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +---------------- +StockMaster v2.0 +---------------- +Data is being extracted from: ./StockMasterData.txt +Welcome to StockMaster, where you can master the knowledge on your Stock! +Out-of-stock Items: +No items out of stock +Low-on-stock Items: (less than 10) +No items low on stock +Enter Command: +---------------- +Inventory is being saved to :./StockMasterData.txt +---------------- +Transactions are being saved to:./TransactionLogs.txt +---------------- +Promotions are being saved to: ./PromotionStorage.txt +---------------- +Thank you for using StockMaster, hope we have helped your lazy ass! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..ae3bc0a936 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1 @@ -James Gosling \ No newline at end of file +exit \ No newline at end of file