diff --git a/.gitignore b/.gitignore index 2873e189e1..5d0d6199fb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,10 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +#log files +*.log +*.log.lck + +#data directory +/data/ \ No newline at end of file diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..2b7058e698 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: logic.MainLogic + diff --git a/OrderAddCommandLogger.log b/OrderAddCommandLogger.log new file mode 100644 index 0000000000..2124312e79 --- /dev/null +++ b/OrderAddCommandLogger.log @@ -0,0 +1,27 @@ + + + + + 2024-04-14T17:09:15.919736900Z + 1713114555919 + 736900 + 0 + OrderAddCommandLogger + INFO + command.order.OrderAddCommand + execute + 1 + Adding new item to order + + + 2024-04-14T17:09:15.925742100Z + 1713114555925 + 742100 + 1 + OrderAddCommandLogger + INFO + command.order.OrderAddCommand + execute + 1 + Item successfully added to order + diff --git a/build.gradle b/build.gradle index ea82051fab..593bf8299e 100644 --- a/build.gradle +++ b/build.gradle @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/data/menus.txt b/data/menus.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/restaurant.txt b/data/restaurant.txt new file mode 100644 index 0000000000..71b344056e --- /dev/null +++ b/data/restaurant.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..d82368c04e 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,8 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:------------:|:--------------:|:---------: +![](https://media.licdn.com/dms/image/D4D03AQHqwchTapLQSw/profile-displayphoto-shrink_800_800/0/1689641178627?e=1715817600&v=beta&t=0zJsfORUyukckyySHLfJWKJGEdSk6vE4IBQLC0tEiPo) | Zheng Wentao | [Github](https://github.com/Zhengwinter/) | [Portfolio](team/zhengwinter.md) +![](https://media.licdn.com/dms/image/D5635AQF9CsBIurtIhQ/profile-framedphoto-shrink_400_400/0/1709822298987?e=1710486000&v=beta&t=ZFPmBTwS2j4itxdUfOToKvL_Wd55X4m0V1_SBnHl_Go) | Xiao Bo | [Github](https://github.com/Xb990219) | [Portfolio](team/xb990219.md) +![](https://avatars.githubusercontent.com/u/110774216?s=400&u=432994bf671e960f6fdab112d9471db24cd57332&v=4) | Zeng Zheqi | [Github](https://github.com/adamzzq) | [Portfolio](team/adamzzq.md) +![](https://media.licdn.com/dms/image/D5603AQFfWFYq1mqgcA/profile-displayphoto-shrink_400_400/0/1696897863941?e=1715212800&v=beta&t=TLopY2QuxEZz9oFQttl_5avFfWdGFdeMX25c60Tnvl8) | Webster Tan | [Github](https://github.com/webtjs) | [Portfolio](team/webtjs.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..361168c523 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,682 @@ # Developer Guide -## Acknowledgements +## Design -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +### Architecture +![Architecture](images/Architecture.png) -## Design & implementation +The **Architecture diagram** given above shows the high-level design of the application. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +Given below is a quick overview of main components and how they interact with each other. + +**`Main`** is in charge of the app launch, shut down, and taking user input. + +* At the app launch, it loads the data from the file system, and initializes other components. +* At the app shut down, it saves the data to the file system, and shuts down other components. + +The app's work is done by the following components: + +* [**`UI`**](#UI-component): The parser that parses user input to command. +* [**`MainLogic`**](#logic-component): The main logic command executor. +* [**`SubLogic`**](#logic-component): The sub-logic (i.e. **`MenuLogic`**, **`OrderLogic`**, **`StatsLogic`**) command executor. +* [**`Model`**](#model-component): The data model that stores the data. +* [**`Command`**](#command-component): Represents a command that the user can execute. +* [**`Storage`**](#storage-component): Reads data from and writes data to the save data file. + +**How the architecture components interact with each other** + +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues +the command `create order -menu 1`, `add -item 1` and `complete`. + +![Sequence Diagram](images/ArchitectureSequenceDiagram.png ) + +### UI Component +The UI component is responsible for parsing user input into commands that can be executed by the logic component. +There is an `analyzeInput` method in the `Parser` class that interprets the user input and classifies +it into a `CommandType` enum.
+The `splitInput` method is used to split the user input into an array of strings, according to the command type. +It returns an array containing any arguments that are needed to execute the command. + +### Logic Component +The logic component consists of classes that handle the logic of the application. The logic component is divided into +`MainLogic` and `SubLogic` which consist of `OrderLogic`, `MenuLogic` and `StatsLogic`. + +* [**`MainLogic`**](#model-component): A class to handle the first level commands, and pass the user input to corresponding +classes for analysis and execution. +* [**`OrderLogic`**](#model-component),[**`MenuLogic`**](#model-component) and [**`StatsLogic`**](#model-component): A class to handle the second level commands, +and pass the user input to corresponding classes for analysis and execution. + +### Command Component +The command component consists of Four different command interfaces: `MainCommand`, `OrderCommand`, `MenuCommand` and `StatsCommand`. The `MainCommand` +interface is for the various command classes that are used in the `MainLogic`, while the `OrderCommand` interface is for +command classes used in `OrderLogic`, and the `MenuCommand` interface is for command classes used in `MenuLogic`. The `StatsCommand` interface is for command classes used in `StatsLogic`. + +Based on the command entered by the user (e.g., `MainHelpCommand` object is created when the user inputs the `help` +command). The `execute()` method of the `Command` object is then called to execute the command, which may require +certain arguments based on the type of command. + +### Model Component +The model consists of classes describing the objects used in this application. +The general structure is that menu and order are separate, but they both work with `menuItem(s)`, which +represent food items on the menu. + +* **`ItemManager`**: An interface containing methods representing operations common to **`Menu`** + and **`Order`**.

+* **`Item`**: An abstract class representing a food item. It should be implemented by **`MenuItem`**. +

+* **`Menu`**: A class representing the menu(s) of the restaurant, where each contains menuItem(s) + that can be ordered. Multiple menus can exist and each has a unique ID.

+* **`MenuItem`**: A class inheriting from `Item`, and represents a food item on the menu.

+* **`Order`**: A class representing an order to be entered into the system to be kept track of. Each + order has a unique ID generated from the time of order.

+* **`Restaurant`**: A class representing the restaurant which stores the restaurant information such as name and + address.

+ +The *Class Diagram* below shows how the model components interact with each other, including interactions such as +dependencies, associations and inheritance. + +![Class Diagram](images/modelcomponent.png) + +### Storage component +The storage component consists of a `Storage` class with various static methods that will be called by `MainLogic` and +the `Command` objects created in `MainLogic`. These methods will be called whenever restaurant information is updated, +a new order or menu is completed, and when an existing menu is edited and completed. Restaurant information, orders, +and menus are saved in three separate text files: `restaurant.txt`, `orders.txt`, and `menus.txt`. + +When the application is launched, `MainLogic` calls the `checkRestaurantData()` method in `Storage ` to check for an +existing restaurant data file in the data folder and attempt to load its data. If the `restaurant.txt` file is missing +or corrupted, the user will be prompted to enter a restaurant name and address, which is then automatically saved in the +save file. + +`MainLogic` then calls the `loadData()` method in `Storage` to load existing order and menu data from `orders.txt` and +`menus.txt` into the application. If `Storage` detects that the save files are corrupted, the corresponding save file +will be deleted from the data folder. + +When new orders and menus have been created and completed, and once control is passed back from `SubLogic` to `MainLogic`, +`MainLogic` will then call either `saveOrder()` or `saveMenu()` depending on what was created. The newly created +order/menu will then be saved into their respective save file. + +After the user edits a menu and completes it with the `complete` command, the `execute()` method of the `Command` object +created by `MainLogic` will then call the `updateMenus()` method in `Storage`. This will save all the changes made to +the menu into the `menus.txt` save file. The `execute()` method then returns to `MainLogic` for further commands. +
+ +The following _sequence diagram_ shows an example of how `Storage` interacts with the other components as described +above. + +![Storage sequence diagram](images/Storage.png) + +## Implementation + +### `MainLogic` +Generally, the main logic works as follows: +1. User enters an input which is received in the *ui* and parsed by the `Parser`. +2. The `Parser` classifies the command based on `CommandType`. +3. If it is a first level command, `execute` is called on the corresponding class. +4. If it is not a first level command, the command will be pass to `SubLogic` to handle. + +**Create Order**
+`Mainlogic` takes user input and creates an `Order` class , then passes it to `OrderLogic` to execute the command. + +**View Order by ID**
+`Mainlogic` takes in the command and the order ID, execute the `view order` command by calling a static method +in `ViewOrderCommand` class. + +**View all orders**
+`Mainlogic` takes in the command and calls the `ViewOrdersSummaryCommand` class to execute the command +by querying the orderList. + +**View Receipt**
+`Mainlogic` takes in the command and calls the `ViewReceiptCommand` class to execute the command + +
+ +### `OrderLogic` +The following *sequence diagram* shows an example of how the user's input is processed by `OrderLogic`. + +![OrderLogic sequence diagram](images/OrderLogicSequenceDiagram.png) + +Generally, the order logic works as follows: +1. User enters an input which is received in the *ui* and parsed by the `Parser`. +2. The `Parser` classifies the command based on `CommandType` +3. Within `OrderLogic`, `execute` is called on the corresponding class +4. Control is passed to other sections of the code + +**View Menu**
+Within the construct of the order logic, the menu can be accessed for viewing in order to select items from +available menus. This is carried out with the `view menu` command. + +**View Items** +Within `OrderLogic`, a list containing all the items that have been added to the current active order can be viewed by executing +the `view item` command. + +**Add** +Inside `OrderLogic`, items from the menu can be added into the current active order. +This is carried out using the `add -item -quantity ` command, +where `` is an integer corresponding to the item's id in the menu, +and `` is an integer of the amount of that item to be added. + +**Delete** +In `OrderLogic`, items from the current order can be removed via the +`delete -item -quantity ` command. `` +and `` are the same type of parameters as the ones specified +in the `Add` command class. + +**Complete** +In `OrderLogic`, once the order is finished, it can be completed and closed +by executing the `complete` command. This marks the current order as completed +and the program returns back to `MainLogic` for subsequent command executions. + +**Cancel** +In "OrderLogic", the user can cancel the current order by executing the `cancel` command. +This will abort the current order created and return to the main menu. +
+ +### `MenuLogic` +![MenuLogic sequence diagram](images/MenuLogicSequenceDiagram.png) + +Generally, the menu logic works similarly to order logic: +1. User enters an input which is received in the *ui* and parsed by the `Parser`. +2. The `Parser` classifies the command based on `CommandType` +3. Within `MenuLogic`, `execute` is called on the corresponding class +4. Control is passed to other sections of the code + +**View Items** +Within `MenuLogic`, a list containing all the items that have been added to the current active order can be viewed by executing +the `view item` command. + +**Add** +Inside `MenuLogic`, items from the menu can be added into the current active order. +This is carried out using the `add -item -price ` command, +where `` is a string representing the name of the MenuItem, +and `` is a double representing the price of that item to be added. + +**Delete** +In `MenuLogic`, items from the current order can be removed via the +`delete -item ` command. `` is the index of the item in the menu. + +**Complete** +Inside `MenuLogic`, once the order is finished, it can be completed and closed +by executing the `complete` command. This marks the current Menu as completed +and the program returns back to `MainLogic` for subsequent command executions. + +**Cancel** +In `MenuLogic`, the user can cancel the current menu by executing the `cancel` command. +This will abort the current menu created and return to the main menu. + + +
+ +### `StatsLogic` +![Statistics sequence diagram](images/StatsLogicSequenceDiagram.png) + +Generally, the stats logic works similarly to order logic: +1. User enters an input which is received in the *ui* and parsed by the `Parser`. +2. The `Parser` classifies the command based on `CommandType` +3. Within `StatsLogic`, `execute` is called on the corresponding class +4. Control is passed to OrderStatistics class to calculate the performance statistics + +**Bestselling**
+In `StatsLogic`, the best-selling item(s) can be viewed by executing the `bestselling` command. +It calls the `getBestSellingItem()` method from an `OrderStatistics` instance to calculate the best-selling item(s) +based on the total count of the items in the ordersList. The ordersList is stored in an ArrayList in MainLogic. + +**Total Orders**
+In `StatsLogic`, the total number of orders can be viewed by executing the `total orders` command. +It calls the `getOrderCount()` method from an `OrderStatistics` instance, +which will iterate through the orders in the ArrayList stored in MainLogic to count the total number of orders. + +**Revenue - Gross**
+In `StatsLogic`, the gross revenue can be viewed by executing the `revenue -gross` command. +It calls the `getGrossRevenue()` method from an `OrderStatistics` instance, which will iterate through the orders in the ArrayList +stored in MainLogic and aggregate the total revenue of each order by calling the `getTotalPrice()` method in the Order class. + +**Revenue - Net**
+In `StatsLogic`, the net revenue can be viewed by executing the `revenue -net` command. +It calls the `getNetRevenue()` method from an `OrderStatistics` instance, which will iterate through the orders in the ArrayList +stored in MainLogic and aggregate the total revenue of each order by calling the `getTotalPrice()` method in the Order class. +The net revenue is calculated by subtracting the total revenue from the total service charge and GST. + +**View Profit - Cost**
+In `StatsLogic`, the profit can be viewed by executing the `view profit -cost ` command. +It calls the `getProfit()` method from an `OrderStatistics` instance, which will iterate through the orders in the ArrayList +stored in MainLogic and aggregate the total revenue of each order by calling the `getTotalPrice()` method in the Order class. +The profit is calculated by subtracting the total revenue from the total service charge and GST, and then subtracting the cost +of the items sold. + +**Quit**
+In `StatsLogic`, the user can return to the main interface by executing the `quit` command. ## Product scope ### Target user profile -{Describe the target user profile} +* has a need to manage orders in a restaurant +* has a need to manage menus in a restaurant +* has a need to manage cashiering duty in a restaurant +* has a need to check the restaurant's performance +* prefers CLI apps to GUI apps +* can type fast + ### Value proposition -{Describe the value proposition: what problem does it solve?} +* Manage orders and menus faster and more efficiently than traditional GUI applications for faster typers. +* Keep track of the restaurant's daily performance with the stats feature. ## 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| +| Priority | As a ... | I want to ... | So that I can ... | +|------------|------------------|-------------------------------------------|------------------------------------------------------------------| +| `* * *` | restaurant owner | add dishes to an order | easily refer and calculate the total price | +| `* * *` | restaurant owner | delete dishes from an order | remove the dishes that the customers do not want | +| `* *` | restaurant owner | view the order receipt | check the order details and the total price | +| `* * *` | restaurant owner | manage cashiering duties in my restaurant | keep track of the money that comes in and out of the restaurant | +| `* *` | restaurant owner | manage menus in my restaurant | keep track of the dishes that are available in the restaurant | +| `* *` | restaurant owner | add new items to a menu | adjust menu offerings to customer demand | +| `* *` | restaurant owner | delete items from a menu | remove items that are no longer available | +| `* *` | restaurant owner | delete menus | remove menus that are no longer needed | +| `* *` | restaurant owner | cancel editing a menu | discard changes made to the menu | +| `* *` | restaurant owner | complete editing a menu | save the changes made to the menu | +| `* ` | restaurant owner | view the menus | check the dishes that are available in the restaurant | +| `* ` | restaurant owner | know the most popular dishes | prepare more raw materials accordingly | +| `* ` | restaurant owner | know the total number of orders | hire the right number of staff | +| `* ` | restaurant owner | know the total revenue | know the restaurant's daily performance | +| `* ` | restaurant owner | indicate cashier's name on the receipt | know who is the cashier that served the customer | +| `* *` | advanced user | change restaurant name and address | update the restaurant's information | +| `* * *` | user | quit the application | exit the and save the data | + ## Non-Functional Requirements -{Give non-functional requirements} +* Should Work on any mainstream OS as long as it has Java 11 or above installed. +* Should be able to handle and save a large number of orders and menus for restaurant's daily operations. +* Faster typist can use the CLI app more efficiently than GUI app ## Glossary -* *glossary item* - Definition +* **Mainstream OS**: Windows, Linux, macOS. + ## Instructions for manual testing +Written below are instructions to test the app manually. + +### Launch and shutdown +* Initial launch + 1. Download the jar file and copy into an empty folder + 2. Open a terminal and navigate to the folder containing the jar file + 3. Run the command `java -jar DinEz.jar` to launch the application +* Shutdown + 1. Type `exit` and press enter to shut down the application + +### Add item to a menu +* Adding an item to a menu + 1. Prerequisite: Type `create menu` in the Main interface to go into the Menu interface. + 2. Test case: `add -item Beef noodles -price 6` + Expected: A new item with the name `Beef noodles` and a price of `$6.00` is added to the menu. A success message + should also appear. + 3. Test case: `add -item Beef noodles -price abc` + Expected: Item is not added to menu. Error message appears asking users to check if they have entered the necessary + parameters correctly. + 4. Other incorrect add commands to try: `add`, `add -item -price`, `add -item 123 -price 12`, `...` + Expected: Similar to previous. + +### Delete item in the menu +* Deleting an item from the menu based on its ID + 1. Prerequisite: Have already added some items to the menu with the `add` command. + 2. Test case: `delete -item 1` + Expected: Item with ID 1 is deleted from the menu. Message appears indicating the name of the removed item has + been deleted. + 3. Test case: `delete -item abc` + Expected: No item is deleted. Error message appears asking users to check if they have entered the necessary + parameters correctly. + 4. Other incorrect commands to try: `delete`, `delete -item x`, `...` (where x is larger than the last item ID in the + menu) + Expected: Similar to previous. + +### Create order and add/delete/view item +* To create an order + 1. Prerequisite: Have already created one menu. Type `create order -menu 1` in Main interface to enter the Order + interface, 1 is the ID of the previously created menu. + 2. Test Case: `Add -item 1 -quantity 2` + Expected: Item 1 is added to the order with quantity 2 + 3. Test Case: `View item` + Expected: Item 1 is displayed with quantity 2 + 4. Test Case: `Delete -item 1 -quantity 1` + Expected: Item 1 is removed from the order with quantity 1 + +### View created orders +* To view all orders or details of a specific order that has been created + 1. Prerequisite: Must have already created and completed orders. + 2. Test case: `view -order -all` + Expected: A list of order IDs should appear with their corresponding total price + 3. Test case: `view -order ` (where `` is an order ID from the list obtained from + `view -order -all`) + Expected: A receipt on the order should appear on the screen. + 4. Test case: `view -order abc` + Expected: Error message appears asking users to check if they have entered the necessary parameters correctly. + 5. Other incorrect commands to try: `view -order`, `view -order x`, `...` (where x is an order ID that does not exist + in the list of orders from `view -order -all`) + Expected: Similar to previous. + +### View created menus +* To view all menus or details of a specific menu that has been created. + 1. Prerequisite: Must have already created and completed menus. + 2. Test case: `view -menu -all` + Expected: A list of menu IDs should appear with their corresponding amount of items that the menu contains + 3. Test case: `view -menu 1` + Expected: Details of the menu should appear which includes the ID, name and price of every item in the menu. + 4. Test case: `view -menu abc` + Expected: Error message appears asking users to check if they have entered the necessary parameters correctly. + 5. Other incorrect commands to try: `view -menu`, `view -menu x`, `...` (where x is a menu ID that does not exist + in the list of menus from `view -menu -all`) + Expected: Similar to previous. + +### Loading data +All save files should be located in the data folder, and the data folder must be in the same directory as the jar file. + +#### Load restaurant data +In the data folder, create a file named `restaurant.txt` and enter the following data: +``` +Techno Edge | 2 Engineering Drive 4 +``` + +#### Load order data +In the data folder, create a file named `orders.txt` and enter the following data: +``` +Techno Edge | 2 Engineering Drive 4 +20240412112658 | Tom | Dine in +1 | Kimchi noodles | 4.0 | 2 +3 | Chicken rice | 3.5 | 1 +- + +``` +> [!NOTE] +> * There **must** be a newline after the `-` which indicates the end of the order. If the newline is omitted and the user +> uses program to create new orders, it will result in the **LOSS OF ORDER DATA** the next time the program is +> launched. + +#### Load menu data +In the data folder, create a file named `menus.txt` and enter the following data: +``` +1 +1 | Chicken Rice | 3.5 +2 | Nasi Lemak | 3.0 +3 | Hokkien Mee | 4.0 +4 | Mee Siam | 3.5 +5 | Fishball Noodles | 3.0 +6 | Chicken Curry Rice | 5.0 +7 | Seafood Fried Rice | 5.5 +8 | Roasted delight set | 6.5 +9 | Hotplate beef set | 7.0 +10 | Kimchi noodles | 4.0 +- + +``` +> [!NOTE] +> * There **must** be a newline after the `-` which indicates the end of the menu. If the newline is omitted and the user +> uses the program to create new menus, it will result in the **LOSS OF MENU DATA** the next time the program is launched. + +#### View loaded data +Launch the application by running the `DinEz.jar` file which is in the same directory as the data file. The application +should load the data from the saved files and the user should see the following: +``` +Hello from DinEz +Enter user name: + +``` + +Once the user has entered their user name, they should see the following when `view restaurant` is entered in the main +interface: +``` +Restaurant name: Techno Edge +Restaurant address: 2 Engineering Drive 4 +``` + +The user should see the following when `view -order 20240412112658` is entered +in the main interface: +``` ++-----------------------------------------------------+ +| RECEIPT | ++-----------------------------------------------------+ +| Techno Edge | +| 2 Engineering Drive 4 | +| | +| Order Type: Dine in | +| Order ID: 20240412112658 | ++-----------------------------------------------------+ +| Item ID | Name | Unit Price | Quantity | ++-----------------------------------------------------+ +| 1 | Kimchi noodles | $4.00 | 2 | +| 3 | Chicken rice | $3.50 | 1 | ++-----------------------------------------------------+ +| Subtotal: $11.50 | ++-----------------------------------------------------+ +| Service Charge (10.0%): $1.15 | +| GST (9.0%): $1.14 | +| Grand Total: $13.79 | ++-----------------------------------------------------+ +| Cashier: Tom | ++-----------------------------------------------------+ + +``` + +The user should see the following when `view -menu 1` is entered in the main +interface: +``` ++------------------------------------------+ +| MENU | ++------+-----------------------------------+ +| ID | Name | Price | ++------+-----------------------------------+ +| 1 | Chicken Rice | $3.50 | +| 2 | Nasi Lemak | $3.00 | +| 3 | Hokkien Mee | $4.00 | +| 4 | Mee Siam | $3.50 | +| 5 | Fishball Noodles | $3.00 | +| 6 | Chicken Curry Rice | $5.00 | +| 7 | Seafood Fried Rice | $5.50 | +| 8 | Roasted delight set | $6.50 | +| 9 | Hotplate beef set | $7.00 | +| 10 | Kimchi noodles | $4.00 | ++------+-----------------------------------+ + +``` + +### Saving data +Data is automatically saved into the respective save files once the corresponding action has been completed. The +following tests uses the loaded data from [Loading Data](#loading-data) + +#### Saving restaurant information +After launching the application, enter `edit restaurant` in the main interface and enter `Fine Food` when prompted for +restaurant name, and `Avenue 0` when prompted for restaurant address. The new restaurant information will then be +automatically saved once the message `Restaurant info has been updated.` appears. The user should see the following: +``` +[Main interface] >>> edit restaurant +Enter restaurant name: +Fine Food +Enter address of restaurant: +Avenue 0 +Restaurant info has been updated. +[Main interface] >>> +``` + +#### Saving orders +After launching the application, enter `create order -menu 1` in the main interface. Enter `1` when prompted for the +order type (dine in/takeaway). Enter the following commands in sequence inside the order interface: +`add -item 2 -quantity 3`, `add -item 4 -quantity 1`. Afterwards, enter `complete` in the order interface and type `y` +when the confirmation message appears. The completed order is then automatically saved in the `orders.txt` file inside +the data folder. The user should see the following: +> [!NOTE] +> * `order_id` is just a placeholder for the actual ID of the order depending on the time the user creates the order and +> differs from order to order. +> * `...` represents abbreviated messages to shorten the example output + +``` +[Main interface] >>> create order -menu 1 +Would you like your order to be + 1) dine in + 2) takeaway +Please enter 1 or 2: +1 +Order order_id creating... +Here are the list of available commands: + help: Shows all the commands that can be used. + ... + cancel: Aborts the current order and returns to the main menu. +[Order: order_id] [Menu: 1] >>> add -item 2 -quantity 3 +3 Nasi Lemak is added to order +[Order: order_id] [Menu: 1] >>> add -item 4 -quantity 1 +1 Mee Siam is added to order +[Order: order_id] [Menu: 1] >>> complete +... +WARNING: Once an order is completed, you are NOT ALLOWED to edit or delete it. +Do you want to complete the order? (type 'y' to complete, anything else to cancel) +y +Order order_id is completed! +[Main interface] >>> +``` + +#### Saving menus +After launching the application, enter `create menu` in the main interface. Next, enter the following commands in +sequence inside the menu interface: `add -item Beef noodles -price 6`, `add -item Prawn noodles -price 5`. Enter +`complete` in the menu interface, and the newly created menu will be saved in the `menus.txt` file inside the data +folder. The user should see the following: +> [!NOTE] +> * `...` represents abbreviated messages to shorten the example output + +``` +[Main interface] >>> create menu +Initializing menu 2... +Here are the list of available commands: + help: Shows all the commands that can be used. + ... + cancel: Aborts the current menu and returns to the main menu. +[Menu: 2] >>> add -item Beef noodles -price 6 +Item successfully added to menu! ++------------------------------------------+ +| MENU | ++------+-----------------------------------+ +| ID | Name | Price | ++------+-----------------------------------+ +| 1 | Beef noodles | $6.00 | ++------+-----------------------------------+ + +[Menu: 2] >>> add -item Prawn noodles -price 5 +Item successfully added to menu! ++------------------------------------------+ +| MENU | ++------+-----------------------------------+ +| ID | Name | Price | ++------+-----------------------------------+ +| 1 | Beef noodles | $6.00 | +| 2 | Prawn noodles | $5.00 | ++------+-----------------------------------+ + +[Menu: 2] >>> complete +Menu 2 has been saved! +[Main interface] >>> +``` + +#### Viewing the saved data +Inside the data folder, open `restaurant.txt`. It should be populated with the following data: +``` +Fine Food | Avenue 0 +``` +Inside the data folder, open `orders.txt`. It should be populated with the following data: +``` +Techno Edge | 2 Engineering Drive 4 +20240412112658 | Tom | Dine in +1 | Kimchi noodles | 4.0 | 2 +3 | Chicken rice | 3.5 | 1 +- +Fine Food | Avenue 0 +20240414012735 | Jack | Dine in +2 | Nasi Lemak | 3.0 | 3 +4 | Mee Siam | 3.5 | 1 +- + +``` +Inside the data folder, open `menus.txt`. It should be populated with the following data: +``` +1 +1 | Chicken Rice | 3.5 +2 | Nasi Lemak | 3.0 +3 | Hokkien Mee | 4.0 +4 | Mee Siam | 3.5 +5 | Fishball Noodles | 3.0 +6 | Chicken Curry Rice | 5.0 +7 | Seafood Fried Rice | 5.5 +8 | Roasted delight set | 6.5 +9 | Hotplate beef set | 7.0 +10 | Kimchi noodles | 4.0 +- +2 +1 | Beef noodles | 6.0 +2 | Prawn noodles | 5.0 +- + +``` + +### Corrupted save files +Generally, if a save file is corrupted, it will be deleted from the data folder. Hence, users **should not** edit the +save files directly, else they risk losing all their data. + +#### Corrupted restaurant save file +In the data folder, locate `restaurant.txt` and open it. Fill in the file with the following data: +``` +Fine Food Avenue 8 +``` +Upon launching the application by running the jar file, the program will prompt the user for a restaurant name and +address, similar to when launching the application for the first time. The user should see the following: +``` +Hello from DinEz +Enter restaurant name: + +``` + +#### Corrupted order save file +In the data folder, locate `orders.txt` and open it. Fill in the file with the following data: +``` +Fine Food | Avenue 0 +20240414012735 | Jack | Dine in +2 | Nasi Lemak | 3.0 | 3 +4 | Mee Siam 3.5 abcdef +- + +``` +Upon launching the application by running the jar file, the program detects that the order save file is corrupted +and deletes `orders.txt` from the data folder. The user should see the following (provided the restaurant save +file is not corrupted): +``` +Hello from DinEz +Order data corrupted, erasing data... +Enter user name: + +``` + +#### Corrupted menu save file +In the data folder, locate `menus.txt` and open it. Fill in the file with the following data: +``` +02 +1 | Beef noodles | abcabc +2 | Prawn noodles | 5.0 +- +``` +Upon launching the application by running the jar file, the program detects that the menu save file is corrupted +and deletes `menus.txt` from the data folder. The user should see the following (provided the restaurant save +file is not corrupted): +``` +Hello from DinEz +Menu data corrupted, erasing data... +Enter user name: -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +``` diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..e5a743021e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ -# Duke +# DINEZ + +DinEz is an integrated system solution which aims to address certain aspects of restaurant management encountered during +the daily operation of a restaurant in a digitalised way, including order taking, menu management and order statistics. -{Give product intro here} Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UMLdiagrams/Architecture.puml b/docs/UMLdiagrams/Architecture.puml new file mode 100644 index 0000000000..58fcc98651 --- /dev/null +++ b/docs/UMLdiagrams/Architecture.puml @@ -0,0 +1,32 @@ +@startuml +!include +!include +!include +!include style.puml + +Class "<$user>" as User #red +Class "<$documents>" as File #blue + +Package " "<>{ + Class Main #grey + Class MainLogic MAIN_LOGIC_COLOR + Class Command COMMAND_COLOR + Class SubLogic SUB_LOGIC_COLOR + Class Storage STORAGE_COLOR + Class Model MODEL_COLOR + Class Parser PARSER_COLOR +} +User .left.> Main : input +Main .right.> User : output +Main .left.> MainLogic +MainLogic -down.> SubLogic #green +MainLogic -left.> Parser #green +Main -up-> Storage #green +MainLogic -up> Model #green +SubLogic -left.> Parser #red +SubLogic -down-> Model #red +Command -down-> Model #lightgreen +Storage -right.> File #black +Parser -down.> Command #orange + +@enduml \ No newline at end of file diff --git a/docs/UMLdiagrams/ArchitectureSequenceDiagram.puml b/docs/UMLdiagrams/ArchitectureSequenceDiagram.puml new file mode 100644 index 0000000000..e23b269328 --- /dev/null +++ b/docs/UMLdiagrams/ArchitectureSequenceDiagram.puml @@ -0,0 +1,68 @@ +@startuml +!include style.puml + +Actor User as user USER_COLOR +Participant ":MainLogic" as mainLogic MAIN_LOGIC_COLOR +Participant ":OrderLogic" as subLogic SUB_LOGIC_COLOR +Participant ":Parser" as parser PARSER_COLOR +Participant ":Command" as command COMMAND_COLOR +Participant ":Model" as model MODEL_COLOR +participant ":Storage" as storage STORAGE_COLOR + + +user -> mainLogic : "Create order -menu 01" +activate mainLogic MAIN_LOGIC_COLOR +mainLogic -> parser : analyzeInput("Create order -menu 01") +activate parser PARSER_COLOR +parser --> mainLogic : return command +deactivate parser +mainLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> model : get(menu01) +activate model MODEL_COLOR +model --> command : return menu01 +deactivate model +command -> model : createOrder(menu01) +activate model MODEL_COLOR +model --> command : return newOrder +deactivate model +command --> subLogic : return newOrder +deactivate command +activate subLogic SUB_LOGIC_COLOR +user -> subLogic : "add -item 001" +subLogic -> parser : analyzeInput("add -item 001") +activate parser PARSER_COLOR +parser --> subLogic : return command +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> model : get(item001) +activate model MODEL_COLOR +model --> command : return item001 +deactivate model +command -> model : addItem(newOrder, item001) +activate model MODEL_COLOR +model --> command : return newOrder +deactivate model +command --> subLogic : return newOrder +deactivate command +user -> subLogic : "complete" +subLogic -> parser : analyzeInput("complete") +activate parser PARSER_COLOR +parser --> subLogic : return command +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command --> subLogic : return newOrder +deactivate command +subLogic --> mainLogic : return newOrder +deactivate subLogic +mainLogic -> storage : save(newOrder) +activate storage STORAGE_COLOR +storage --> mainLogic +deactivate storage + + + + +@enduml \ No newline at end of file diff --git a/docs/UMLdiagrams/MenuLogicSequenceDiagram.puml b/docs/UMLdiagrams/MenuLogicSequenceDiagram.puml new file mode 100644 index 0000000000..bb72e3cd20 --- /dev/null +++ b/docs/UMLdiagrams/MenuLogicSequenceDiagram.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml + +Actor User as user USER_COLOR + +Participant ":MenuLogic" as subLogic SUB_LOGIC_COLOR +Participant ":Parser" as parser PARSER_COLOR +Participant ":Command" as command COMMAND_COLOR +Participant ":Model" as model MODEL_COLOR + + +user -> subLogic : modifyMenu() +activate subLogic SUB_LOGIC_COLOR +subLogic -> parser : analyzeInput("Create Menu") +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> model : create new Menu +activate model MODEL_COLOR +model --> command +deactivate model +command --> subLogic +deactivate command +user -> subLogic : "add -item -price " +subLogic -> parser : analyzeInput +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> model : create new MenuItem +activate model MODEL_COLOR +model --> command +deactivate model +command --> subLogic +deactivate command +user -> subLogic : "view item" +subLogic -> parser : analyzeInput("view item") +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> model : get all MenuItems +activate model MODEL_COLOR +model --> command +deactivate model +command --> subLogic +deactivate command +user --> subLogic : "delete -item " +subLogic -> parser : analyzeInput("delete -item ") +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> model : delete MenuItem +activate model MODEL_COLOR +model --> command +deactivate model +command --> subLogic +deactivate command +@enduml \ No newline at end of file diff --git a/docs/UMLdiagrams/OrderLogicSequenceDiagram.puml b/docs/UMLdiagrams/OrderLogicSequenceDiagram.puml new file mode 100644 index 0000000000..21dbc2b3e5 --- /dev/null +++ b/docs/UMLdiagrams/OrderLogicSequenceDiagram.puml @@ -0,0 +1,73 @@ +@startuml +!include style.puml + +Actor User as user USER_COLOR + +Participant ":OrderLogic" as subLogic SUB_LOGIC_COLOR +Participant ":Parser" as parser PARSER_COLOR +Participant ":Command" as command COMMAND_COLOR +Participant ":Menu" as menu MENU_COLOR +Participant ":Order" as order ORDER_COLOR + + +activate subLogic SUB_LOGIC_COLOR +user -> subLogic : "add -item -quantity " +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> menu : getItemByID(itemID) +activate menu MENU_COLOR +menu --> command : item +deactivate menu +command -> order : add(item) +activate order ORDER_COLOR +order --> command +deactivate order +command --> subLogic +deactivate command + +user -> subLogic : "view item" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> order : toString() +activate order ORDER_COLOR +order --> command +deactivate order +command --> subLogic +deactivate command + +user -> subLogic : "delete -item -quantity " +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> order : remove(itemID) +activate order ORDER_COLOR +order --> command +deactivate order +command --> subLogic +deactivate command + +user -> subLogic : "view menu" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> menu : toString() +activate menu MENU_COLOR +menu --> command +deactivate menu +command --> subLogic +deactivate command +@enduml \ No newline at end of file diff --git a/docs/UMLdiagrams/StatsLogicSequenceDiagram.puml b/docs/UMLdiagrams/StatsLogicSequenceDiagram.puml new file mode 100644 index 0000000000..e9dda5cda7 --- /dev/null +++ b/docs/UMLdiagrams/StatsLogicSequenceDiagram.puml @@ -0,0 +1,83 @@ +@startuml +!include style.puml + +Actor User as user USER_COLOR + +Participant ":StatsLogic" as subLogic SUB_LOGIC_COLOR +Participant ":Parser" as parser PARSER_COLOR +Participant ":Command" as command COMMAND_COLOR +Participant ":OrderStatistics" as stats ORDER_STATS_COLOR + + +activate subLogic SUB_LOGIC_COLOR +user -> subLogic : "bestselling" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> stats : getBestSellingItems() +activate stats ORDER_STATS_COLOR +stats --> command : return bestSellingItems +deactivate stats +command --> subLogic +deactivate command + +user -> subLogic : "revenue -gross" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> stats : getGrossRevenue() +activate stats ORDER_STATS_COLOR +stats --> command : return grossRevenue +deactivate stats +command --> subLogic +deactivate command + +user -> subLogic : "revenue -net" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> stats : getNetRevenue() +activate stats ORDER_STATS_COLOR +stats --> command : return netRevenue +deactivate stats +command --> subLogic +deactivate command + +user -> subLogic : "view profit -cost" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> stats : getProfit(cost) +activate stats ORDER_STATS_COLOR +stats --> command : return profit +deactivate stats +command --> subLogic +deactivate command + +user -> subLogic : "total orders" +subLogic -> parser : analyzeInput(inputText) +activate parser PARSER_COLOR +parser --> subLogic +deactivate parser +subLogic -> command : execute(command) +activate command COMMAND_COLOR +command -> stats : getOrderCount() +activate stats ORDER_STATS_COLOR +stats --> command : return orderCount +deactivate stats +command --> subLogic +deactivate command + +@enduml \ No newline at end of file diff --git a/docs/UMLdiagrams/Storage.puml b/docs/UMLdiagrams/Storage.puml new file mode 100644 index 0000000000..db6601c96d --- /dev/null +++ b/docs/UMLdiagrams/Storage.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml + +Actor User as user USER_COLOR + +Participant ":MainLogic" as mainLogic MAIN_LOGIC_COLOR +Participant ":Parser" as parser PARSER_COLOR +Participant ":Command" as command COMMAND_COLOR +Participant ":MenuLogic" as menuLogic SUB_LOGIC_COLOR +Participant ":OrderLogic" as orderLogic SUB_LOGIC_COLOR +Participant ":Model" as model MODEL_COLOR +Participant ":Storage" as storage STORAGE_COLOR + +activate mainLogic MAIN_LOGIC_COLOR +mainLogic -> storage : checkRestaurantData(...) +activate storage STORAGE_COLOR +storage --> mainLogic : isRestaurantLoaded +deactivate storage + +alt isRestaurantLoaded == false + mainLogic -> model : initRestaurant(...) + activate model MODEL_COLOR + model --> mainLogic + deactivate model + mainLogic -> storage : saveRestaurant(...) + activate storage STORAGE_COLOR + storage --> mainLogic + deactivate storage + deactivate model +end +mainLogic -> storage : loadData(...) +activate storage STORAGE_COLOR +storage --> mainLogic +deactivate storage + +user -> mainLogic : "edit restaurant" +ref over mainLogic, model : enter new restaurant name and address +mainLogic --> storage : saveRestaurant(...) +activate storage STORAGE_COLOR +storage --> mainLogic +deactivate storage + +user -> mainLogic : "create menu" +ref over mainLogic, parser, command, model : create new menu and complete it +mainLogic -> storage : saveMenu(...) +activate storage STORAGE_COLOR +storage --> mainLogic +deactivate storage + +user -> mainLogic : "create order -menu 1" +ref over mainLogic, parser, command, model : create new order and complete it +mainLogic -> storage : saveOrder(...) +activate storage STORAGE_COLOR +storage --> mainLogic +deactivate storage + +user -> mainLogic : "edit -menu 1" +ref over mainLogic, parser, command, model : edit menu 1 and complete it +command -> storage : updateMenus(...) +activate storage STORAGE_COLOR +storage --> command +deactivate storage +command --> mainLogic + +@enduml diff --git a/docs/UMLdiagrams/model.puml b/docs/UMLdiagrams/model.puml new file mode 100644 index 0000000000..aea2374cfd --- /dev/null +++ b/docs/UMLdiagrams/model.puml @@ -0,0 +1,82 @@ +@startuml +'https://plantuml.com/class-diagram + +skinparam dpi 600 +hide circle +skinparam classAttributeIconSize 0 + +abstract class Item << abstract >>{ + -id + - name + - unitPrice + + + Item (id: String, name: String, unitPrice: double) + +getID() : String + +getName() : String + +getPrice() : double + +} +interface ItemManager << interface >>{ + add(var1: MenuItem) : boolean + remove(var1: String) : boolean + getID() : String +} +class Menu { +- logr +- menuID +- menuItemList +- setupLogger() : void ++ Menu(menuID: String) ++ getItemByID(itemID: String) : Optional ++ getItemByName(itemName: String) : Optional ++ getMenuItemList() : ArrayList ++ getSize() : int ++ getMenuSummary() : String ++ getID() : String ++ add(item: MenuItem) : boolean ++ remove(itemID: String) : boolean ++ toString() : String +} +class MenuItem { ++ MenuItem(id: String, name: String, unitPrice: double) ++ toString(): String ++ compareTo(o: MenuItem) : int +} +class Order { +- SERVICE_CHARGE +- GST +- orderID +- restaurantName +- restaurantAddress +- orderType +- userName +- orderItemList ++ add(item: MenuItem) : boolean ++ remove(itemID: String) : boolean ++ remove(item: MenuItem) : boolean ++ getItemCount(itemID String): int ++ getID() : String ++ getSize() : int ++ getTotalPrice() : double ++ getReceipt() : String ++ getRestaurantName() : String ++ getRestaurantAddress() : String ++ getUserName() : String ++ getOrderType() : String ++ getOrderItemList() : ArrayList ++ getOrderSummary() : String ++ toString() : String +} + +Item <|-- MenuItem + +ItemManager <|.. Menu +ItemManager <|.. Order + +MenuItem "1..*" --* Order +MenuItem "1..*" -* Menu + + + + +@enduml \ No newline at end of file diff --git a/docs/UMLdiagrams/style.puml b/docs/UMLdiagrams/style.puml new file mode 100644 index 0000000000..349194a8b1 --- /dev/null +++ b/docs/UMLdiagrams/style.puml @@ -0,0 +1,62 @@ +/' + 'Commonly used styles and colors across diagrams. + 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more + 'comprehensive list of skinparams. + '/ + +!define MAIN_LOGIC_COLOR #green +!define COMMAND_COLOR #lightgreen +!define SUB_LOGIC_COLOR #red +!define ORDER_COLOR #cornflowerblue +!define MENU_COLOR #mediumorchid +!define STORAGE_COLOR #black +!define MODEL_COLOR #brown +!define PARSER_COLOR #orange +!define USER_COLOR #000000 +!define ORDER_STATS_COLOR #FF66CC + + +skinparam Package { + BackgroundColor #FFFFFF + BorderThickness 1 + FontSize 16 +} + +skinparam Class { + FontColor #FFFFFF + FontSize 15 + BorderThickness 1 + BorderColor #FFFFFF + StereotypeFontColor #FFFFFF + FontName Arial +} + +skinparam Actor { + BorderColor USER_COLOR + Color USER_COLOR + FontName Arial +} + +skinparam Sequence { + MessageAlign center + BoxFontSize 15 + BoxPadding 0 + BoxFontColor #FFFFFF + FontName Arial +} + +skinparam Participant { + FontColor #FFFFFFF + Padding 20 +} + +skinparam ArrowFontStyle bold +skinparam MinClassWidth 50 +skinparam ParticipantPadding 10 +skinparam Shadowing false +skinparam DefaultTextAlignment center +skinparam packageStyle Rectangle + +hide footbox +hide members +hide circle \ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..bc236a4321 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,41 +2,458 @@ ## Introduction -{Give a product intro} +DinEz is an integrated system solution which aims to address certain aspects of restaurant management encountered during +the daily operation of a restaurant in a digitalised way, including order taking, menu management and order statistics. + +If you are well-versed with CLI and can type fast, DinEz can help to coordinate the various tasks and allow you to +access all the necessary information in one application. The CLI is also more reliable and simpler to operate than a +traditional GUI. + +* [Quick start](#quick-start) +* [Features](#features) + * [User help guide: `help`](#user-help-guide-help) + * [Create new menu: `create menu`](#create-new-menu-create-menu) + * [Edit menu: `edit menu`](#edit-menu-edit-menu) + * [Create new order: `create order`](#create-new-order-create-order) + * [Check orders: `view order`](#check-orders-view-order) + * [Get order receipt: `receipt order`](#get-order-receipt-receipt-order) + * [Check menus: `view menu`](#check-menu-items-view-menu) + * [Check restaurant info: `view restaurant`](#check-restaurant-info-view-restaurant) + * [Edit restaurant info: `edit restaurant`](#edit-restaurant-info-edit-restaurant) + * [Check order statistics: `view performance`](#check-order-statistics-view-performance) + * [Quit the program: `bye`](#quit-the-program-bye) + * [Save data](#save-data) + * [Editing the data file](#editing-the-data-file) +* [Features in Menu Interface](#features-in-menu-interface) + * [Menu help guide: `help`](#menu-help-guide-help) + * [Add items: `add`](#add-items-add) + * [Delete items: `delete`](#delete-items-delete) + * [Check menu items: `view items`](#check-menu-items-view-items) + * [Complete current menu: `complete`](#complete-current-menu-complete) + * [Abort current menu: `cancel`](#abort-current-menu-cancel) +* [Features in Order Interface](#features-in-order-interface) + * [Order help guide](#order-help-guide-help) + * [Add items: `add`](#add-items-add-1) + * [Delete items: `delete`](#delete-items-delete-1) + * [Check menu items: `view menu`](#check-menu-items-view-menu) + * [Check order items: `view items`](#check-order-items-view-items) + * [Complete current order: `complete`](#complete-current-order-complete) + * [Abort current order: `cancel`](#abort-current-order-cancel) +* [Features in Statistics interface](#features-in-statistics-interface) + * [Bestselling item: `bestselling`](#find-bestselling-items-bestselling) + * [Total orders: `total orders`](#get-total-orders-count-total-orders) + * [Calculate gross or net revenue: `revenue`](#calculate-gross-or-net-revenue-revenue) + * [View profit: `view profit`](#calculate-profit-view-profit) + * [Return to main interface: `quit`](#return-to-main-interface-quit) +* [FAQ](#faq) +* [Command summary](#command-summary) ## Quick Start -{Give steps to get started quickly} +1. Ensure you have Java `11` installed on your computer. +2. Download the latest `DinEz.jar` from [here](https://github.com/AY2324S2-CS2113-F14-2/tp/releases) +3. Copy the file to the folder you want to use as the _home folder_ for the restaurant management system +4. Open a command terminal, `cd` into the folder in which you placed the `DinEz.jar` then use the command +`java -jar DinEz.jar` to run the program. +5. You should be prompted to enter a restaurant name, address, and a username. After entering the required information, + you should see a greeting message. The following is an example of what you should see: + ``` + Hello from DinEz + Enter restaurant name: + Fine Food + Enter address of restaurant: + Avenue 0 + Enter user name: + Tom + Here are the list of available commands: + help: Shows all the commands that can be used. + create order -menu : Creates a new order using the specified menu. + view -order -all: Shows a brief summary of all the created orders. + view -order : Shows all the contents of a specified order. + receipt -order : shows the receipt of the specified order. + create menu: Creates a new menu. + edit -menu : Modify the specified menu's items in the menu interface. + view -menu : Shows all the contents of a specified menu. + view -menu -all: Shows a brief summary of all the created menus. + view performance: Enters the order statistics interface. + bye: Quits the program. + ``` +6. Type the command in the CLI and press Enter to execute it e.g. typing `help` then pressing + Enter will display the help menu. + Here are some example commands you can try: + - `help`: Displays all the commands that can be used + - `create order -menu 1`: Creates a new order which uses the menu of ID `1` + and navigates to the order interface to perform sub-commands + - `view -menu -all`: Shows a brief summary of all the created menus + - `bye`: Exits from the program +7. **IMPORTANT!!!** Our application consists of **four interfaces**(i.e. main, order, menu and statistics) with different + commands available. Remember to type `help` to check available commands at current interface. +8. Do note that by default, no menu is present and a menu has to be created before the items on the menu + can be added to an order. +9. Refer to the [Features](#features) below for details of each command + + +> [!NOTE] +> * All the spaces in the commands are for clarity and are **optional** to include in the actual command.
+> * Commands are **case-insensitive**.
+> * The `` are used to denote the parameters that should be replaced with the **actual user input**. + + +Accepted formats examples:
+`create order -menu 1` +`create order-menu1` +`createorder-menu1` +`CREATE ORDER -MENU 1` +`Createorder -menu 1`(Non-exhaustive list) + + +## Features + +### User help guide: `help` +Shows all the commands that can be used, including their format and their expected purpose. + +Format: `help` + +### Create new menu: `create menu` +Creates a new menu and goes to the menu interface to perform sub-commands. (Refer to +[Features for Menu interface](#features-in-menu-interface) for the sub-commands) + +Format: `create menu` + +### Edit menu: `edit menu` +Edits a menu by going to the menu interface to perform sub-commands. (Refer to +[Features for Menu interface](#features-in-menu-interface) for the sub-commands) + +Format: `edit -menu ` + +* The `` is a number representing the menu ID. +* The `` can be obtained from the `view -menu -all` command. + +Example of usage: + +`edit -menu 1` + +### Create new order: `create order` + +*NOTE: A menu has to be present before an order can be created. For a first-time user, a menu should be created first.* + +Creates a new order with a specified menu and goes to the order interface to perform sub-commands. (Refer to +[Features for Order interface](#features-in-order-interface) for the sub-commands) + +Format: `create order -menu ` + +* The `` is a number representing the menu ID. You can obtain this ID with the help of the `view -menu -all` + command. + +Example of usage: + +`create order -menu 1` +`create order -menu 23` + +### Check orders: `view order` +Shows all the contents of a specified order, or shows a brief summary of all orders. + +Format: `view -order ` and `view -order -all` + +* The `` is a unique number representing the order. This ID can be obtained from the + `view -order -all` command. + +Example of usage: + +`view -order -all` + +`view -order 20240403022723` + +### Get order receipt: `receipt order` +Shows the receipt of a specified order. + +Format: `receipt -order ` + +* The `` is a unique number representing the order. This ID can be obtained from the + `view -order -all` command. + +Example of usage: + +`receipt -order 20240403022723` + +### Check menus: `view menu` +Shows all the contents of a specified menu, or shows the brief summary of all menus. + +Format: `view -menu ` and `view -menu -all` + +* The `` is a unique number representing a selected menu. +This ID can be obtained from the `view -menu -all` command. + +Example of usage: + +`view -menu -all` + +`view -menu 2` + +### Check restaurant info: `view restaurant` +Shows the restaurant name and address currently in use. + +Format: `view restaurant` + +### Edit restaurant info: `edit restaurant` +Changes the restaurant name and address. + +Format: `edit restaurant` + +### Check order statistics: `view performance` +Enters the order statistics interface where various statistics based on all completed orders can be viewed. (Refer to +[Features in statistics interface](#features-in-statistics-interface) for the sub-commands) + +Format: `view performance` + +### Quit the program: `bye` +Exits out of the program. + +Format: `bye` + +### Save data +Data for restaurant information, orders, and menus are saved in the hard disk automatically after any command that +changes the data. There is no need to save manually. + +### Editing the data file +Data for restaurant information, orders, and menus are saved automatically in 3 separate text files `restaurant.txt`, +`orders.txt`, and `menus.txt`. These 3 files are located in `[JAR file location]/data/*.txt` (where `*.txt` represents +the name of the data file). **ONLY** edit the data files directly **if** you are an advanced user. +> [!CAUTION] +> * If your changes to the data files make its format invalid, our application will discard the files that have been + corrupted and start with the respective empty data files at the next run. Therefore, it is recommended to make a + backup of your data files before editing it. +> * Additionally, some edits can cause DinEz to behave in unexpected ways (e.g., if order ID is changed to a string, + multiple orders have the same order ID, multiple menus have the same menu ID etc.). Hence, only edit the data files + if you are confident that you can update it correctly. + +## Features in Menu interface + +### Menu help guide: `help` +Shows all the commands that can be used in the menu interface. + +Format: `help` + +### Add items: `add` +Adds an item with specified ID, name, and price to the current menu. + +Format: `add -item -price ` + +* The `` should contain **English alphabets and spaces** only. +* The `` should be the price of the item without the `$` symbol between 0 and 10000 (both exclusive). + +Example of usage: -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +`add -item Chicken Rice -price 3.50` -## Features +`add -item Seafood Fried Rice -price 5` -{Give detailed description of each feature} +### Delete items: `delete` +Deletes an item of a specified ID from the current menu. -### Adding a todo: `todo` -Adds a new item to the list of todo items. +Format: `delete -item ` -Format: `todo n/TODO_NAME d/DEADLINE` +* The `` is a number representing the menu item. You can obtain this ID with the help of the `view items` + command. -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Example of usage: -Example of usage: +`delete -item 1` -`todo n/Write the rest of the User Guide d/next week` +### Check menu items: `view items` +Shows all the items in the current menu. + +Format: `view items` + +### Complete current menu: `complete` +Marks the current menu as completed and returns to the main interface. + +Format: `complete` + +* The `complete` command cannot be used on an empty menu. You will be prompted to add items to the menu. + +### Abort current menu: `cancel` +Cancels the current menu's creation and returns to the main interface. + +Format: `cancel` + +* The cancelled menu will be discarded and cannot be retrieved. + +## Features in Order interface + +### Order help guide: `help` +Shows all the commands that can be used in the order interface. + +Format: `help` + +### Add items: `add` +Adds a specified quantity of a specific menu item into the order. + +Format: ` add -item -quantity ` + +* The `` is a unique number that identifies the item. You can obtain this ID using the `view menu` command. +* The `` is a number representing the amount of the specified item to add + +Example of usage: + +`add -item 1 -quantity 2` + +### Delete items: `delete` +Deletes a specified quantity of a specified item in the order. + +Format: `delete -item -quantity ` + +* The `` is a number representing the menu item. You can obtain this ID with the help of the `view menu` + command. + +Example of usage: + +`delete -item 1 -quantity 1` + +### Check menu items: `view menu` +Shows the menu used by the current order. + +Format: `view menu` + +### Check order items: `view items` +Shows all the items in the current order. + +Format: `view items` + +### Complete current order: `complete` +Marks the current order as completed and returns to the main interface. + +Format: `complete` or `complete -discount ` + +* The `complete` command cannot be used on an empty order. You will be prompted to add items to the order. +* The `` is a number representing the discount percentage. It should be an integer without the `%` + symbol, ranging from 0 to 99. + +### Abort current order: `cancel` +Cancels the current order's creation and returns to the main interface. + +Format: `cancel` + +* The cancelled order will be discarded and cannot be retrieved. + + +## Features in Statistics interface + +### Find bestselling items: `bestselling` +Shows the best-selling item(s) in the restaurant of all time. +The best-selling item is defined as the item with the highest quantity sold. If there are multiple items with the same +highest quantity sold, up to 3 of the best-selling items will be displayed (based on the natural order of their Id). + +Format: `bestselling` + +### Get total orders count: `total orders` +Shows the total number of orders that have been created. + +Format: `total orders` + +### Calculate gross or net revenue: `revenue` +Shows the gross revenue of the restaurant. (i.e. total revenue before deducting costs and taxes), +or shows the net revenue of the restaurant. (i.e. total revenue after deducting taxes) + +Format: `revenue -gross` or `revenue -net` + + +### Calculate profit: `view profit` +Shows the profit based on the operating costs in the restaurant as provided by the user. +(i.e. total revenue after deducting costs and taxes)

+The cost here is intended as the total cost of operations, which can include +production, rental and supply costs. + +Format: `view profit -cost ` + +* The `` is the money spent on preparing the items. +* It should be a non-negative number without the `$` symbol. + +Example of usage: +`view profit -cost 150` +`view profit -cost 20000` + +### Return to main interface: `quit` +Returns to the main interface from the statistics interface. + +Format: `quit` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` ## FAQ -**Q**: How do I transfer my data to another computer? +**Q**: How do I save restaurant information, as well as the menus and orders I have created? + +**A**: Data for restaurant information, orders, and menus are saved in the hard disk automatically after any command +that changes the data. There is no need to save manually. The data files are stored in the `data` directory, which is +located in the same directory where you put the `DinEz.jar` file. + +**Q**: How many menus can I have? + +**A**: Just like the orders, there is no limit to the number of menus that can be created. The goal of this system is +to be able to allow the user to create multiple menus from which items for the order can be chosen from. + +**Q** What happens if I try to add similar items to the menu? + +**A**: This is not allowed. The item name and ID are unique in each menu. + +**Q**: Can I delete an order that I have entered? + +**A**: No, once an order has been created, it cannot be deleted. This is to ensure that the order history is preserved +as they are important for the restaurant's records. + +**Q**: Can I edit an order that I have entered? + +**A**: To preserve the integrity of the generated orders, we do not offer a feature to edit orders that have been +entered. A confirmation message is hence present for the user to verify that their inputs are correct. + +**Q**: What is the difference between net revenue and profit? + +**A**: As mentioned in the user guide for those features, the difference between net revenue and profit is that the +former includes costs of production, but the latter excludes it. -**A**: {your answer here} ## Command Summary -{Give a 'cheat sheet' of commands here} +### Main interface commands +* User help guide `help` +* Check menus `view -menu ` and `view -menu -all` +* Create new menu `create menu` <<[Menu Interface](#menu-interface-sub-commands)>> +* Edit menu `edit -menu ` <<[Menu Interface](#menu-interface-sub-commands)>> +* Check orders `view -order ` and `view -order -all` +* Create new order `create order -menu `<<[Order Interface](#order-interface-sub-commands)>> +* Get order receipt `receipt -order ` +* Check restaurant info `view restaurant` +* Edit restaurant info `edit restaurant` +* Check order statistics `view performance`<<[Statistics interface](#statistics-interface-sub-commands)>> +* Quit the program `bye` + +### Menu interface sub-commands + * Add items `add -item -price ` + * Delete items `delete -item ` + * Check menu items `view items` + * Complete current menu `complete` + * Abort current menu `cancel`
+ +### Order interface sub-commands + * Add items `add -item -quantity ` + * Delete items `delete -item -quantity ` + * Check menu items `view menu` + * Check order items `view items` + * Complete current order `complete` or `complete -discount ` + * Abort current order `cancel` -* Add todo `todo n/TODO_NAME d/DEADLINE` +### Statistics interface sub-commands +* Find bestselling items: `bestselling` +* Get total orders count: `total orders` +* Calculate Gross revenue: `revenue -gross` +* Calculate Net revenue: `revenue -net` +* Calculate profit: `view profit -cost ` +* Return to main interface: `quit` diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png new file mode 100644 index 0000000000..23f33f7905 Binary files /dev/null and b/docs/images/Architecture.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png new file mode 100644 index 0000000000..c852fdc4cd Binary files /dev/null and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/MenuLogicSequenceDiagram.png b/docs/images/MenuLogicSequenceDiagram.png new file mode 100644 index 0000000000..acd2697cb8 Binary files /dev/null and b/docs/images/MenuLogicSequenceDiagram.png differ diff --git a/docs/images/OrderLogicSequenceDiagram.png b/docs/images/OrderLogicSequenceDiagram.png new file mode 100644 index 0000000000..7b1f1ed096 Binary files /dev/null and b/docs/images/OrderLogicSequenceDiagram.png differ diff --git a/docs/images/StatsLogicSequenceDiagram.png b/docs/images/StatsLogicSequenceDiagram.png new file mode 100644 index 0000000000..f71172feab Binary files /dev/null and b/docs/images/StatsLogicSequenceDiagram.png differ diff --git a/docs/images/Storage.png b/docs/images/Storage.png new file mode 100644 index 0000000000..92f5d6f2fa Binary files /dev/null and b/docs/images/Storage.png differ diff --git a/docs/images/modelcomponent.png b/docs/images/modelcomponent.png new file mode 100644 index 0000000000..b7aad82bc0 Binary files /dev/null and b/docs/images/modelcomponent.png differ diff --git a/docs/team/adamzzq.md b/docs/team/adamzzq.md new file mode 100644 index 0000000000..6713d9d75e --- /dev/null +++ b/docs/team/adamzzq.md @@ -0,0 +1,68 @@ +# Zeng Zheqi - Project Portfolio Page +## Project: DinEZ +### Overview +DinEz is a CLI application designed for restaurant managers to manage menus, orders, and statistics. +It is written in Java and has about 5kLoC. + +### Summary of contribution +#### **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=adamzzq&tabRepo=AY2324S2-CS2113-F14-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +#### **New Features**: +- Design and implement `Parser` and related command type classes with regular expressions + * What it does: parse user inputs into commands and arguments which can be executed by the respective logic components + * Justification: The parsing capability streamlined the interaction between users and the system, + improving usability and efficiency. Leveraging on regular expressions, it allows more flexible user inputs with minimal change in + code while keeping its robustness to invalid inputs. For example, some common careless mistakes made in typing with + extra or missing spaces between command words, or incorrect capitalisation, are taken care of with small tweaks in the + regular expression, while still able to detect incorrect commands or arguments. + * Highlights: + * Making use of Java's Stream API and functional programming concepts, significantly shortens the code length in parsing + inputs as compared to explicitly checking inputs in non-declarative programming ways, thereby also increases the readability of the code. + * It laid the foundation for future development of command-based functionalities in the project by increasing + the scalability. + +- Implement the `Statistics` feature + * What it does: Provides users with statistical insights such as total orders, revenue, bestsellers, etc. + * Justification: Restaurant managers often need to track the performance of their restaurants, and this feature + provides them with the necessary data to make informed decisions. + * Highlights: + * Add `StatsLogic`, `StatsCommand`, and `Statatistics` related classes in packages to handle the statistics feature, + which separates the logic of statistics from other features, keeping the codebase modular and maintainable. + * Utilised Java's generics to implement a flexible pair class to store the statistics data, which is extensible to + handle more statistics in the future. + +#### **Enhancements to existing features**: +- Improve the display format of receipt and menu in the `Order` and `Menu` features + * Allow long item names and restaurant information to be displayed in multiple lines to prevent the receipt and menu + from being too wide. + * Sort the item IDs after deletion in the menu to ensure the item IDs are continuous and consistent. + * Add subtotal, tax and gst to the receipt to provide a more detailed breakdown of the order. + +- Implement limit-checking mechanism for the `Order` and `Menu` features + * Prevent users from adding more items than the limit set by the system, thereby preventing digits overflow in the + receipt and menu. + * Prevent users from adding unreasonably expensive items to the menu. + * Provide users with feedback on the limit reached when adding items. + +#### **Project management**: +- Assisted in the release of `v1.0` and `v2.1` on GitHub. +- Created labels and categorised issues on GitHub +- Created and managed milestones v1.0 and assigned issues to the respective milestones + +#### **Documentation**: +- User Guide: + * Added documentation for the `Statistics` feature + * Added documentation for the `Menu` and `Order` features + * Added documentation for the `Help` command +- Developer Guide: + * Added `UI` component description in the architecture section + * Added descriptions and sequence diagram for the `StatsLogic` component + * Added some user stories about the `Statistics`, `Menu`, and `Order` features + +#### **Community**: +- Reviewed other team's DG and UG to give constructive feedback and suggestions + + + + + 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/webtjs.md b/docs/team/webtjs.md new file mode 100644 index 0000000000..b874bc2c54 --- /dev/null +++ b/docs/team/webtjs.md @@ -0,0 +1,67 @@ +# Webster Tan's Project Portfolio Page + +## Overview + +### Project DinEz + +DinEz is a Command Line Interface (CLI) application used for managing various aspects of restaurant management such as +order taking, menu management, and order statistics. This app is designed for users who are familiar with CLI and can +type quickly. It is written in Java and has about 5 kLoC. + +### Summary of contributions + +* **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=webtjs&tabRepo=AY2324S2-CS2113-F14-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **New Feature**: <<*Menu Commands*>> Added create menu command + * What it does: Allows users to create a new menu where they can add new items into the menu. + * Justification: Restaurants have different menus depending on the circumstance (Breakfast Menu, Lunch Menu, + Dinner Menu etc.). Hence, this feature enables restaurant managers to create different menus according to their needs. + * Highlights: Utilised Java's `Optional` class to indicate an absence of return value and to chain method calls, + thereby shortening the code. + +* **New Feature**: <<*Menu Commands*>> Added edit menu command + * What it does: Allows users to make changes to a menu they have created such as adding new items or deleting existing + items. + * Justification: Restaurants occasionally revamp their menus due to various reasons such as experimenting new items, + price adjustments, and cutting out items that are not selling well. This feature will provide restaurant managers + with a convenient way to update their menus. + * Highlights: Utilised Java's `Optional` class to differentiate between `cancel` and `complete` command in the Menu + Interface. Doing so allows changes made to the menu to be discarded for a `cancel` command. + * Credits: Modifying the menu (e.g. adding items, deleting items etc.) reuses the code from `MenuLogic`, which was + mostly written by Zhengwinter and modified by all other team members. + +* **New Feature**: <<*Storage*>> Added storage feature for restaurant details, menus, and orders + * What it does: Allows users to store their restaurant details (restaurant name & address), menus they create, and + orders created into a local save file. This save file can be read and the data will be loaded into the program when + the user launches the application again. + * Justification: Restaurant managers need to be able to be able to keep track of past orders for reasons such as + accounting and data analysis, which can then in turn boost sales. + * Highlights: This feature required careful error handling due to the need to read and write to files. + +* **Enhancements to existing features**: + * Added more descriptive error messages for invalid commands (Pull requests + [#132](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/132), [#135](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/135)) + * Feature to revert changes made to menu when editing a menu via a `cancel` command + (Pull request [#137](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/137)) + * Wrote tests for `MainCommand` (commands in `MainLogic`) to achieve coverage of 90% (Pull requests + [#77](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/77), [#80](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/80) [#180](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/180)) + +* **Documentation**: + * User Guide: + * Added documentation for Introduction, Quick Start, Features, and Command Summary (Pull request [#91](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/91)) + * Added documentation for saving and editing data (Pull request [#170](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/170)) + * Developer Guide: + * Fix issues with Developer Guide raised by TA (Pull request [#161](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/161)) + * Added implementation details and sequence diagram for `OrderLogic`. (Pull request [#161](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/161)) + * Added manual testing instructions for viewing created menus and orders. (Pull request [#161](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/161)) + * Added manual testing instructions for saving data, loading data, and corrupting data. (Pull request [#161](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/161)) + * Added implemenation details and sequence diagram for `Storage`. (Pull requests [#185](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/185), [#189](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/189)) + +* **Community**: + * Assisted in resolving forum issues(e.g. [forum issue #38](https://github.com/nus-cs2113-AY2324S2/forum/issues/38#issuecomment-2049258461)) + * PRs reviewed (with non-trivial review comments): [#164](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/164), + [#168](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/168) + +* **Project Management**: + * Managed release `v2.0` on GitHub + * Assisted release `v2.1` on GitHub diff --git a/docs/team/xb990219.md b/docs/team/xb990219.md new file mode 100644 index 0000000000..4a67b0f530 --- /dev/null +++ b/docs/team/xb990219.md @@ -0,0 +1,43 @@ +# Xiao Bo's Project Portfolio Page + +## Overview + +DinEz is a CLI application intended to help user to manage the menus and orders of a restaurant. +It is written in Java and has about 5kLoC. + +### Summary of Contributions + +* **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=functional-code&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=Xb990219&tabRepo=AY2324S2-CS2113-F14-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **New Feature**: Design and implement the overall structure of the application, including the main logic, order logic of the application. + * What it does: Combine different features into the same class, making the application more maintainable and extensible. + * Justification: Mainlogic class is the core of the application, it is responsible for the overall logic of the application, including the logic of the order, menu, + and the storage. Each command will be executed by the mainlogic class, and future development can be easily added by extending the mainlogic class. + OrderLogic class is entered from the Mainlogic class, it is responsible for the logic of the order, including the logic of adding, deleting, and listing the order. + This design makes the application more maintainable and extensible. + * Highlights: Utilise different level of abstraction to separate the logic of the application, making the application more maintainable and extensible. + +* **New Feature**: Add Order commands, including AddOrderCommand, DeleteOrderCommand, ListOrderCommand, etc. + * What it does: Allows users to add, delete, and list orders in the application. + * Justification: Restaurant managers need to be able to create orders and add item to it, this feature enables restaurant managers to create orders in the application. + * Highlights: Utilise Stream to avoid potential bugs and optional to avoid null pointer exception, making the code more readable. + +* **New Feature**: Add order and item models. + * What it does: Allows users to create orders and add items to it. + * Justification: These models are the core of the application, they are responsible for storing the orders and items, and the logic of the application is based on these models. + * Highlights: Utilise Stream to avoid potential bugs and optional to avoid null pointer exception, making the code more readable. + +* **Project management**: + * Managed and assisted release `v1.0` on GitHub. + +* **Documentation**: + * **User Guide**: + * Add documentation for FAQ + * Add documentation for some order and menu commands + * **Developer Guide**: + * Add documentation for the overall architecture and Mainlogic component. + * Add sequence diagram for the overall architecture and Mainlogic component. + +* **Community**: + * PR reviewed : [#161](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/161#pullrequestreview-1999213158) \ No newline at end of file diff --git a/docs/team/zhengwinter.md b/docs/team/zhengwinter.md new file mode 100644 index 0000000000..1a216adceb --- /dev/null +++ b/docs/team/zhengwinter.md @@ -0,0 +1,57 @@ +# Zheng Wentao - Project Portfolio Page + +## Overview + +### Project: DinEz + +DinEz is a CLI application intended to help someone manage the menus and orders of a restaurant. +It is written in Java and has about 5kLoC. + +### Summary of Contributions + +* **New Feature**: <<*Menu Commands*>> Added the different Menu commands, including MenuHelp, MenuComplete,MenuExit, + MenuDelete + * What it does: Allows the user to navigate around the Menu sub-interface, excluding Menu adding feature + * Justification: An independent set of commands for Menu and Order sub-interfaces allows different + logic to be implemented for the 2 sub-systems + * Highlights: This enhancement paved the way for the implementation of the Menu sub-interface + * Credits: All the code was manually written but modifications were made by other members of the team. +* **New Feature**: <<*Additional order details and receipt formatting*>> Implement additional information that would be + prompted for when building an order + * What it does: Prompts the user for additional details such as restaurant name, restaurant address, order + type and name of user. + * Justification: This feature improves the ability of the CLI application to model a real cashiering system. + These features also make the CLI application more customizable to a variety of potential users. + * Highlights: This enhancement introduced new variables to be kept track of within the implementation of order and + order-related components, and is ultimately reflected in the order receipt. + * Credits: Implementation was mostly done manually, but the proper formatting was done by Zeng Zheqi. + * **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=Zhengwinter&tabRepo=AY2324S2-CS2113-F14-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + * **Project Management**: Assisted in managing `v1.0` and `v2.1` + * **Enhancement to existing feature**: Modified Menu class and MenuLogic (Pull requests [#74](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/74)) + * **Testing**: Wrote test cases for `Menu`, `MenuCommand` and `OrderCommand` + * **Issues**: Raised issues (non-trivial and aesthetics) and found potential bugs: + [#158](https://github.com/AY2324S2-CS2113-F14-2/tp/issues/158) + [#159](https://github.com/AY2324S2-CS2113-F14-2/tp/issues/159) + * **Documentation**: + * **Header comments**: Wrote header comments for various `MainCommands` and `MenuCommands` + * **Developer Guide**: + * Added documentation for all the model components including `ItemManager`, `Menuitem`, `Menu`, `Order` + * **Model Diagram**: Included a class diagram for the model components + * **User guide**: + * Added FAQ to justify why edit order is not implemented + * Reiterate difference between net revenue and profit in FAQ + * Update command summary + * **Proofreading**: + * Caught grammatical and formatting errors in both the UG and DG, relevant PRs: + [#200](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/200), + [#201](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/201) + +* **Community**: + * PR reviewed (with no trivial review comments): [#143](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/143), + [#144](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/144), + [#168](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/168) + * Provided detailed explanation and justification for the majority of PRs (a few examples shown below): + [#72](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/72), + [#85](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/85), + [#141](https://github.com/AY2324S2-CS2113-F14-2/tp/pull/141) \ No newline at end of file diff --git a/src/main/java/command/main/MainCommand.java b/src/main/java/command/main/MainCommand.java new file mode 100644 index 0000000000..bdae2fa831 --- /dev/null +++ b/src/main/java/command/main/MainCommand.java @@ -0,0 +1,5 @@ +package command.main; + +public interface MainCommand { + +} diff --git a/src/main/java/command/main/MainCreateOrderCommand.java b/src/main/java/command/main/MainCreateOrderCommand.java new file mode 100644 index 0000000000..70dcad053e --- /dev/null +++ b/src/main/java/command/main/MainCreateOrderCommand.java @@ -0,0 +1,27 @@ +package command.main; + +import model.Menu; +import ui.Parser; + +import java.util.ArrayList; +import java.util.Optional; + +public class MainCreateOrderCommand implements MainCommand { + /** + * Creates an order from the main interface by selecting items from an existing menu + * @param inputText The input string entered by the user from the command line + * @param menusList The list of all existing menus + * @return the menu that was selected if it is present, otherwise return empty Optional + */ + public static Optional execute(String inputText, ArrayList menusList) { + try { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String menuID = indexString[0]; + return Optional.of(menusList.get(Integer.parseInt(menuID) - 1)); + } catch (IndexOutOfBoundsException e) { + System.out.println("Menu does not exist"); + System.out.println("Order not created"); + } + return Optional.empty(); + } +} diff --git a/src/main/java/command/main/MainEditMenuCommand.java b/src/main/java/command/main/MainEditMenuCommand.java new file mode 100644 index 0000000000..d88fe5e652 --- /dev/null +++ b/src/main/java/command/main/MainEditMenuCommand.java @@ -0,0 +1,46 @@ +package command.main; + +import logic.MenuLogic; +import model.Menu; +import model.MenuItem; +import storage.Storage; +import ui.Parser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Scanner; + +public class MainEditMenuCommand implements MainCommand{ + + + /** + * Process edit menu command from user and allow user to edit a menu if it exists + * @param input The scanner used to receive user input from the command line + * @param inputText The input from the user, in the form of String + * @param menusList The ArrayList that keeps track of all existing menus + */ + public static void execute(Scanner input, String inputText, ArrayList menusList) { + String[] parsedString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String menuID = parsedString[0]; + Optional menuToEdit = menusList.stream().filter(menu -> menu.getId().equals(menuID)).findAny(); + if (menuToEdit.isPresent()) { + Menu copymenu = new Menu(menuToEdit.get().getId()); + for (MenuItem menuItem : menuToEdit.get().getMenuItemList()) { + copymenu.add(menuItem); + } + MenuLogic.modifyMenu(input, copymenu, menusList.toArray().length).ifPresent(menu -> { + int ogIndex = menusList.indexOf(menuToEdit.get()); + menusList.remove(ogIndex); + menusList.add(ogIndex, copymenu); + try { + Storage.updateMenus(menusList); + } catch (IOException e) { + System.out.println("Error updating menus save file."); + } + }); + } else { + System.out.println("Menu ID not found"); + } + } +} diff --git a/src/main/java/command/main/MainEditRestaurantInfoCommand.java b/src/main/java/command/main/MainEditRestaurantInfoCommand.java new file mode 100644 index 0000000000..34192b4201 --- /dev/null +++ b/src/main/java/command/main/MainEditRestaurantInfoCommand.java @@ -0,0 +1,17 @@ +package command.main; + +import model.Restaurant; +import storage.Storage; + +public class MainEditRestaurantInfoCommand { + /** + * Executes the command to edit the restaurant information. + * + * @param restaurant The Restaurant object whose information needs to be edited + */ + public static void execute(Restaurant restaurant) { + restaurant.initRestaurant(); + System.out.println("Restaurant info has been updated."); + Storage.saveRestaurant(restaurant); + } +} diff --git a/src/main/java/command/main/MainExitCommand.java b/src/main/java/command/main/MainExitCommand.java new file mode 100644 index 0000000000..00ad1dcfe9 --- /dev/null +++ b/src/main/java/command/main/MainExitCommand.java @@ -0,0 +1,13 @@ +package command.main; +public class MainExitCommand implements MainCommand { + + /** + * Executes the command to exit the program + * @param isExit the boolean value to exit the program + * @return a boolean value to exit the program + */ + public static boolean execute(boolean isExit) { + System.out.println("Bye. Hope to see you again soon!"); + return true; + } +} diff --git a/src/main/java/command/main/MainHelpCommand.java b/src/main/java/command/main/MainHelpCommand.java new file mode 100644 index 0000000000..545ac84c6f --- /dev/null +++ b/src/main/java/command/main/MainHelpCommand.java @@ -0,0 +1,26 @@ +package command.main; + + +public class MainHelpCommand implements MainCommand { + + /** + * Displays all the available commands that can be used. + */ + public static void execute() { + String helpMessage = "Here are the list of available commands:\n" + + "\thelp: Shows all the commands that can be used.\n" + + "\tcreate order -menu : Creates a new order using the specified menu.\n" + + "\tview -order -all: Shows a brief summary of all the created orders.\n" + + "\tview -order : Shows all the contents of a specified order.\n" + + "\treceipt -order : shows the receipt of the specified order.\n" + + "\tcreate menu: Creates a new menu.\n" + + "\tedit -menu : Modify the specified menu's items in the menu interface.\n" + + "\tview -menu : Shows all the contents of a specified menu.\n" + + "\tview -menu -all: Shows a brief summary of all the created menus.\n" + + "\tedit restaurant: Modify restaurant name and address.\n" + + "\tview restaurant: Shows the restaurant name and address currently in use.\n" + + "\tview performance: Enters the order statistics interface.\n" + + "\tbye: Quits the program."; + System.out.println(helpMessage); + } +} diff --git a/src/main/java/command/main/MainReceiptOrderCommand.java b/src/main/java/command/main/MainReceiptOrderCommand.java new file mode 100644 index 0000000000..2db09e8c3c --- /dev/null +++ b/src/main/java/command/main/MainReceiptOrderCommand.java @@ -0,0 +1,25 @@ +package command.main; + +import model.Order; +import ui.Parser; + +import java.util.ArrayList; + +public class MainReceiptOrderCommand { + + /** + * Executes the command to print the receipt of an order + * @param ordersList the list of orders to choose from + * @param inputText the user input in the form of String from the command line + */ + public static void execute(ArrayList ordersList, String inputText) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String orderID = indexString[0]; + ordersList.stream() + .filter(order -> order.getId().equals(orderID)) + .findAny() + .ifPresentOrElse(x -> { + System.out.println(x.getReceipt()); }, + () -> System.out.println("Order not found")); + } +} diff --git a/src/main/java/command/main/MainViewMenuCommand.java b/src/main/java/command/main/MainViewMenuCommand.java new file mode 100644 index 0000000000..df31a3a319 --- /dev/null +++ b/src/main/java/command/main/MainViewMenuCommand.java @@ -0,0 +1,26 @@ +package command.main; + +import model.Menu; +import java.util.ArrayList; +import ui.Parser; + +/** + * Displays the menu that was specified by the menu ID + */ +public class MainViewMenuCommand { + + /** + * Executes the command to view a menu + * @param menuList the list of menus to choose from + * @param inputText the user input in the form of String from the command line + */ + public static void execute(ArrayList menuList, String inputText) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String menuID = indexString[0]; + menuList.stream() + .filter(menu -> menu.getId().equals(menuID)) + .findAny() + .ifPresentOrElse(System.out::println, () -> System.out.println("Menu not found")); + } + +} diff --git a/src/main/java/command/main/MainViewMenusSummaryCommand.java b/src/main/java/command/main/MainViewMenusSummaryCommand.java new file mode 100644 index 0000000000..e8ccd50525 --- /dev/null +++ b/src/main/java/command/main/MainViewMenusSummaryCommand.java @@ -0,0 +1,23 @@ +package command.main; + +import model.Menu; + +import java.util.ArrayList; + +/** + * Displays a list of all the menu items + */ +public class MainViewMenusSummaryCommand { + + /** + * Executes the command to view the summary of all menus + * @param menuList the list of menus to choose from + */ + public static void execute(ArrayList menuList) { + if (menuList.isEmpty()){ + System.out.println("No menus available"); + return; + } + menuList.forEach(x -> System.out.println(x.getMenuSummary())); + } +} diff --git a/src/main/java/command/main/MainViewOrderCommand.java b/src/main/java/command/main/MainViewOrderCommand.java new file mode 100644 index 0000000000..04f0d5f2b8 --- /dev/null +++ b/src/main/java/command/main/MainViewOrderCommand.java @@ -0,0 +1,24 @@ +package command.main; + +import model.Order; +import ui.Parser; + +import java.util.ArrayList; + +public class MainViewOrderCommand implements MainCommand { + + /** + * Executes the command to view an order + * @param ordersList the list of orders to choose from + * @param inputText the user input in the form of String from the command line + */ + public static void execute(ArrayList ordersList, String inputText) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String orderID = indexString[0]; + ordersList.stream() + .filter(order -> order.getId().equals(orderID)) + .findAny() + .map(Order::getReceipt) + .ifPresentOrElse(System.out::println, () -> System.out.println("Order not found")); + } +} diff --git a/src/main/java/command/main/MainViewOrdersSummaryCommand.java b/src/main/java/command/main/MainViewOrdersSummaryCommand.java new file mode 100644 index 0000000000..e76b549afd --- /dev/null +++ b/src/main/java/command/main/MainViewOrdersSummaryCommand.java @@ -0,0 +1,20 @@ +package command.main; + +import model.Order; + +import java.util.ArrayList; + +public class MainViewOrdersSummaryCommand implements MainCommand { + + /** + * Executes the command to view the summary of all orders + * @param ordersList the list of orders to choose from + */ + public static void execute(ArrayList ordersList) { + if (ordersList.isEmpty()){ + System.out.println("No orders available"); + return; + } + ordersList.forEach(x -> System.out.println(x.getOrderSummary())); + } +} diff --git a/src/main/java/command/main/MainViewRestaurantInfoCommand.java b/src/main/java/command/main/MainViewRestaurantInfoCommand.java new file mode 100644 index 0000000000..26fdc67ee5 --- /dev/null +++ b/src/main/java/command/main/MainViewRestaurantInfoCommand.java @@ -0,0 +1,15 @@ +package command.main; + +import model.Restaurant; + +public class MainViewRestaurantInfoCommand { + /** + * Executes the command to view the restaurant information. + * + * @param restaurant The Restaurant object whose information needs to be displayed + */ + public static void execute(Restaurant restaurant) { + System.out.println("Restaurant name: " + restaurant.getRestaurantName()); + System.out.println("Restaurant address: " + restaurant.getRestaurantAddress()); + } +} diff --git a/src/main/java/command/menu/MenuAddCommand.java b/src/main/java/command/menu/MenuAddCommand.java new file mode 100644 index 0000000000..9c7fcaa4b7 --- /dev/null +++ b/src/main/java/command/menu/MenuAddCommand.java @@ -0,0 +1,43 @@ +package command.menu; + +import model.Menu; +import model.MenuItem; +import ui.Parser; + +import java.util.Optional; + + +public class MenuAddCommand implements MenuCommand { + /** + * Executes the adding of items to a new menu + * @param newMenu the menu to which the item is added to + * @param inputText the user input in the form of String from the command line + */ + public static void execute(Menu newMenu, String inputText) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String itemID = String.valueOf(newMenu.getSize() + 1); + String itemName = indexString[0]; + String itemPrice = indexString[1]; + Optional itemByName = newMenu.getItemByName(itemName); + + // limit price range to 0.01 - 9999.99 + if (Double.parseDouble(itemPrice) < 0.01 || Double.parseDouble(itemPrice) > 9999.99) { + System.out.println("Price must be between 0 and 10000 (both exclusive)"); + return; + } + + if (itemName.isEmpty()) { + System.out.println("Item name cannot be empty"); + return; + } + + if (itemByName.isPresent()) { + System.out.println("Item already in menu. It has ID: " + + itemByName.get().getID() + " and Name: " + itemByName.get().getName()); + } else { + newMenu.add(new MenuItem(itemID, itemName, Double.parseDouble(itemPrice))); + System.out.println("Item successfully added to menu!"); + System.out.println(newMenu); + } + } +} diff --git a/src/main/java/command/menu/MenuCommand.java b/src/main/java/command/menu/MenuCommand.java new file mode 100644 index 0000000000..18681cc5d8 --- /dev/null +++ b/src/main/java/command/menu/MenuCommand.java @@ -0,0 +1,4 @@ +package command.menu; + +public interface MenuCommand { +} diff --git a/src/main/java/command/menu/MenuCompleteCommand.java b/src/main/java/command/menu/MenuCompleteCommand.java new file mode 100644 index 0000000000..94b1f1adae --- /dev/null +++ b/src/main/java/command/menu/MenuCompleteCommand.java @@ -0,0 +1,20 @@ +package command.menu; + +import model.Menu; + +public class MenuCompleteCommand implements MenuCommand { + + /** + * Prints a confirmation message of menu saved to the command line + * @param menu The menu that was edited + * @return boolean indicating completion status of menu editing + */ + public static boolean execute(Menu menu) { + if (menu.getSize() == 0) { + System.out.println("Menu " + menu.getId() + " is empty. Please add items to the menu."); + return false; + } + System.out.println("Menu " + menu.getId() + " has been saved!"); + return true; + } +} diff --git a/src/main/java/command/menu/MenuDeleteCommand.java b/src/main/java/command/menu/MenuDeleteCommand.java new file mode 100644 index 0000000000..ee0fa8187c --- /dev/null +++ b/src/main/java/command/menu/MenuDeleteCommand.java @@ -0,0 +1,23 @@ +package command.menu; + +import model.Menu; +import ui.Parser; + +public class MenuDeleteCommand implements MenuCommand{ + /** + * Executes the command to remove an item from the current menu + * @param menu the menu containing the item to be removed + * @param inputText the user input in the form of String from the command line + */ + public static void execute(Menu menu, String inputText) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText),inputText); + String itemID = indexString[0]; + if (menu.getItemById(itemID).isEmpty()) { + System.out.println("Item not found in menu"); + } else { + System.out.println(menu.getItemById(itemID).get().getName() + " is removed from menu"); + menu.remove(itemID); + } + System.out.println(menu); + } +} diff --git a/src/main/java/command/menu/MenuExitCommand.java b/src/main/java/command/menu/MenuExitCommand.java new file mode 100644 index 0000000000..06c90a5811 --- /dev/null +++ b/src/main/java/command/menu/MenuExitCommand.java @@ -0,0 +1,14 @@ +package command.menu; + +import model.Menu; + +public class MenuExitCommand implements MenuCommand { + + /** + * Outputs confirmation message of cancelling a menu + * @param menu The menu that is being cancelled + */ + public static void execute (Menu menu) { + System.out.println("Menu " + menu.getId() + " cancelled"); + } +} diff --git a/src/main/java/command/menu/MenuHelpCommand.java b/src/main/java/command/menu/MenuHelpCommand.java new file mode 100644 index 0000000000..2548c8135b --- /dev/null +++ b/src/main/java/command/menu/MenuHelpCommand.java @@ -0,0 +1,17 @@ +package command.menu; + +public class MenuHelpCommand implements MenuCommand { + /** + * Provides a guide to the user on the available commands for navigating the Menu sub-interface + */ + public static void execute() { + System.out.println("Here are the list of available commands:"); + System.out.println("\thelp: Shows all the commands that can be used."); + System.out.println("\tadd -item -price : " + + "Adds a new item to the menu."); + System.out.println("\tdelete -item : Deletes an item from the menu."); + System.out.println("\tview items: Shows all the items in the current menu."); + System.out.println("\tcomplete: Completes the menu and returns to the main menu."); + System.out.println("\tcancel: Aborts the current menu and returns to the main menu."); + } +} diff --git a/src/main/java/command/order/OrderAddCommand.java b/src/main/java/command/order/OrderAddCommand.java new file mode 100644 index 0000000000..5e32992607 --- /dev/null +++ b/src/main/java/command/order/OrderAddCommand.java @@ -0,0 +1,78 @@ +package command.order; + +import model.Menu; +import model.MenuItem; +import model.Order; +import ui.Parser; + + +import java.util.Optional; + + + + +public class OrderAddCommand implements OrderCommand { + private static final String LOGGER_NAME = "OrderAddCommandLogger"; + //private static final Logger logr = Logger.getLogger(LOGGER_NAME); + /** + * Executes the command to add a specified quantity of an item to an order. + * + * @param newOrder the order to add the item to + * @param inputText the string containing details of item to be added + * @param menu the menu with the item to be added + */ + public static void execute(Order newOrder, String inputText, Menu menu) { + //OrderAddCommand.setUpLogger(); + + //logr.info("Adding new item to order"); + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String itemID = indexString[0]; + String itemQuantity = indexString[1]; + Optional item = menu.getItemById(itemID); + + // limit quantity to 1 - 9999 + if (Integer.parseInt(itemQuantity) < 1 || Integer.parseInt(itemQuantity) > 9999) { + //logr.warning("Quantity of item is out of range"); + System.out.println("Quantity must be between 0 and 10000 (both exclusive)"); + return; + } + if (item.isPresent()) { + try { + for (int i = 0; i < Integer.parseInt(itemQuantity); i++) { + newOrder.add(item.get()); + } + } catch (IllegalArgumentException e) { + //logr.warning("Item count already at maximum"); + System.out.println(e.getMessage()); + return; + } + //logr.info("Item successfully added to order"); + System.out.println(itemQuantity + " " + item.get().getName() + " is added to order"); + } else { + //logr.warning("Item not in menu, not added to order"); + System.out.println("Item not found in menu"); + } + } + + /* + * Sets up the logger for the OrderAddCommand class. + + + + private static void setUpLogger() { + LogManager.getLogManager().reset(); + logr.setLevel(Level.INFO); + + ConsoleHandler ch = new ConsoleHandler(); + ch.setLevel(Level.SEVERE); + logr.addHandler(ch); + + try { + FileHandler fh = new FileHandler(LOGGER_NAME + ".log"); + fh.setLevel(Level.FINE); + logr.addHandler(fh); + } catch (IOException e) { + logr.log(Level.SEVERE, "File logger not working", e); + } + }*/ +} diff --git a/src/main/java/command/order/OrderCommand.java b/src/main/java/command/order/OrderCommand.java new file mode 100644 index 0000000000..c3bf926074 --- /dev/null +++ b/src/main/java/command/order/OrderCommand.java @@ -0,0 +1,4 @@ +package command.order; + +public interface OrderCommand { +} diff --git a/src/main/java/command/order/OrderCompleteCommand.java b/src/main/java/command/order/OrderCompleteCommand.java new file mode 100644 index 0000000000..0a25fd9f22 --- /dev/null +++ b/src/main/java/command/order/OrderCompleteCommand.java @@ -0,0 +1,46 @@ +package command.order; + +import model.Order; +import ui.Parser; + +import java.util.Scanner; + +public class OrderCompleteCommand implements OrderCommand { + /** + * Executes the command to complete an order. + * + * @param order the order to be completed + * @return true if the order is completed, false otherwise + */ + public static boolean execute(Order order, Scanner input, String inputText) { + if (order.getSize() == 0) { + System.out.println("Order " + order.getId() + " is empty. Please add items to the order."); + return false; + } + + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + double discount; + try { + discount = Integer.parseInt(indexString[1]) / 100.0; + } catch (ArrayIndexOutOfBoundsException e) { + discount = 0.0; + } + + try { + System.out.println(order.getReceipt(discount)); + } catch (NumberFormatException e) { + System.out.println(e.getMessage()); + return false; + } + System.out.println("WARNING: Once an order is completed, you are NOT ALLOWED to edit or delete it."); + System.out.println("Do you want to complete the order? (type 'y' to complete, anything else to cancel)"); + String userInput = input.nextLine(); + if (userInput.equals("y")) { + System.out.println("Order " + order.getId() + " is completed!"); + return true; + } else { + System.out.println("Order " + order.getId() + " is not completed."); + return false; + } + } +} diff --git a/src/main/java/command/order/OrderDeleteCommand.java b/src/main/java/command/order/OrderDeleteCommand.java new file mode 100644 index 0000000000..cd28a648f2 --- /dev/null +++ b/src/main/java/command/order/OrderDeleteCommand.java @@ -0,0 +1,33 @@ +package command.order; + +import model.Menu; +import model.Order; +import ui.Parser; + +public class OrderDeleteCommand implements OrderCommand { + /** + * Executes the command to remove an item from an order. + * + * @param order the order containing the item to be removed + * @param inputText the input containing the details of the item to be removed + * @param menu the menu that the item to be removed is from + */ + public static void execute(Order order, String inputText, Menu menu) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + String itemID = indexString[0]; + String itemQuantity = indexString[1]; + if (order.getItemCount(itemID) == 0) { + System.out.println("Item not found in order"); + } else if (order.getItemCount(itemID) - Integer.parseInt(itemQuantity) <= 0) { + order.remove(itemID); + assert order.getItemCount(itemID) == 0 : "all items of itemID should be removed from the order"; + System.out.println("All " + menu.getItemById(itemID).get().getName() + " is removed from order"); + } else { + for (int i = 0; i < Integer.parseInt(itemQuantity); i++) { + order.remove(menu.getItemById(itemID).get()); + } + System.out.println(itemQuantity + " " + menu.getItemById(itemID).get().getName() + + " is removed from order"); + } + } +} diff --git a/src/main/java/command/order/OrderExitCommand.java b/src/main/java/command/order/OrderExitCommand.java new file mode 100644 index 0000000000..fa5436d379 --- /dev/null +++ b/src/main/java/command/order/OrderExitCommand.java @@ -0,0 +1,15 @@ +package command.order; + +import model.Order; + +public class OrderExitCommand implements OrderCommand { + /** + * Executes the command to exit the order. + * + * @param order the current order that will be cancelled + */ + public static void execute(Order order) { + System.out.println("Order " + order.getId() + " cancelled"); + System.out.println("Order not created"); + } +} diff --git a/src/main/java/command/order/OrderHelpCommand.java b/src/main/java/command/order/OrderHelpCommand.java new file mode 100644 index 0000000000..7c642a5c7e --- /dev/null +++ b/src/main/java/command/order/OrderHelpCommand.java @@ -0,0 +1,19 @@ +package command.order; + +public class OrderHelpCommand implements OrderCommand { + /** + * Executes the command to show the list of available commands. + */ + public static void execute() { + System.out.println("Here are the list of available commands:"); + System.out.println("\thelp: Shows all the commands that can be used."); + System.out.println("\tadd -item -quantity : " + + "Adds the specified quantity \n\t\tof a particular menu item into the order."); + System.out.println("\tdelete -item -quantity : " + + "Deletes the specified quantity \n\t\tof a particular menu item from the order."); + System.out.println("\tview menu: Shows the menu of the current order."); + System.out.println("\tview items: Shows the items in the current order."); + System.out.println("\tcomplete (-discount ): Completes the order and returns to the main menu."); + System.out.println("\tcancel: Aborts the current order and returns to the main menu."); + } +} diff --git a/src/main/java/command/order/OrderViewItemsCommand.java b/src/main/java/command/order/OrderViewItemsCommand.java new file mode 100644 index 0000000000..ed44712363 --- /dev/null +++ b/src/main/java/command/order/OrderViewItemsCommand.java @@ -0,0 +1,18 @@ +package command.order; + +import model.Order; + +public class OrderViewItemsCommand implements OrderCommand { + /** + * Executes the command to list all the items in an order. + * + * @param order the order to be listed + */ + public static void execute(Order order) { + if (order.getSize() == 0) { + System.out.println("Order is empty."); + } else { + System.out.println(order.viewItems()); + } + } +} diff --git a/src/main/java/command/order/OrderViewMenuCommand.java b/src/main/java/command/order/OrderViewMenuCommand.java new file mode 100644 index 0000000000..efaadfd559 --- /dev/null +++ b/src/main/java/command/order/OrderViewMenuCommand.java @@ -0,0 +1,17 @@ +package command.order; + +import command.main.MainCommand; +import model.Menu; + +public class OrderViewMenuCommand implements MainCommand { + + + /** + * Executes the command to view the menu. + * + * @param menu the menu to be viewed + */ + public static void execute(Menu menu) { + System.out.println(menu); + } +} diff --git a/src/main/java/command/stats/StatsBestSellingItemCommand.java b/src/main/java/command/stats/StatsBestSellingItemCommand.java new file mode 100644 index 0000000000..bc3b2ff3d6 --- /dev/null +++ b/src/main/java/command/stats/StatsBestSellingItemCommand.java @@ -0,0 +1,31 @@ +package command.stats; + +import model.Item; +import stats.OrderStatistics; + +import java.util.List; + +public class StatsBestSellingItemCommand implements StatsCommand { + /** + * Prints the best-selling item in the order statistics + * + * @param stats OrderStatistics object containing the statistics + */ + public static void execute(OrderStatistics stats) { + List bestSellingItems = stats.getBestSellingItems(); + if (bestSellingItems.isEmpty()) { + System.out.println("Make some orders to see the best selling items!"); + return; + } + + System.out.println("Best selling item(s):"); + for (int i = 0; i < bestSellingItems.size(); i++) { + System.out.println((i + 1) + ". " + bestSellingItems.get(i).getName()); + if (i > 3) { + System.out.println("Displaying top 3 best selling items..."); + //System.out.println("To view more, please refer to the statistics file"); + break; + } + } + } +} diff --git a/src/main/java/command/stats/StatsCommand.java b/src/main/java/command/stats/StatsCommand.java new file mode 100644 index 0000000000..3de102b5fb --- /dev/null +++ b/src/main/java/command/stats/StatsCommand.java @@ -0,0 +1,4 @@ +package command.stats; + +public interface StatsCommand { +} diff --git a/src/main/java/command/stats/StatsGrossRevenueCommand.java b/src/main/java/command/stats/StatsGrossRevenueCommand.java new file mode 100644 index 0000000000..30f74e13b4 --- /dev/null +++ b/src/main/java/command/stats/StatsGrossRevenueCommand.java @@ -0,0 +1,14 @@ +package command.stats; + +import stats.OrderStatistics; + +public class StatsGrossRevenueCommand implements StatsCommand{ + /** + * Prints the gross revenue in the order statistics + * + * @param stats OrderStatistics object containing the statistics + */ + public static void execute(OrderStatistics stats) { + System.out.printf("Gross revenue: $%.2f\n", stats.getGrossRevenue()); + } +} diff --git a/src/main/java/command/stats/StatsHelpCommand.java b/src/main/java/command/stats/StatsHelpCommand.java new file mode 100644 index 0000000000..a3efb5d0f8 --- /dev/null +++ b/src/main/java/command/stats/StatsHelpCommand.java @@ -0,0 +1,17 @@ +package command.stats; + +public class StatsHelpCommand implements StatsCommand { + /** + * Prints the help message for the stats commands + */ + public static void execute() { + System.out.println("Available commands:"); + System.out.println("\tbestselling - View the best selling item(s)"); + System.out.println("\ttotal orders - View the total number of orders"); + System.out.println("\trevenue -gross - View the gross revenue"); + System.out.println("\trevenue -net - View the net revenue"); + System.out.println("\tview profit -cost - View the profit based on the cost"); + System.out.println("\tquit - return to main interface"); + + } +} diff --git a/src/main/java/command/stats/StatsNetRevenueCommand.java b/src/main/java/command/stats/StatsNetRevenueCommand.java new file mode 100644 index 0000000000..75012a1678 --- /dev/null +++ b/src/main/java/command/stats/StatsNetRevenueCommand.java @@ -0,0 +1,14 @@ +package command.stats; + +import stats.OrderStatistics; + +public class StatsNetRevenueCommand implements StatsCommand { + /** + * Prints the net revenue in the order statistics + * + * @param stats OrderStatistics object containing the statistics + */ + public static void execute(OrderStatistics stats) { + System.out.printf("Net revenue: $%.2f\n", stats.getNetRevenue()); + } +} diff --git a/src/main/java/command/stats/StatsProfitCommand.java b/src/main/java/command/stats/StatsProfitCommand.java new file mode 100644 index 0000000000..598be9e826 --- /dev/null +++ b/src/main/java/command/stats/StatsProfitCommand.java @@ -0,0 +1,18 @@ +package command.stats; + +import stats.OrderStatistics; +import ui.Parser; + +public class StatsProfitCommand implements StatsCommand { + /** + * Prints the profit in the order statistics + * + * @param stats OrderStatistics object containing the statistics + * @param inputText the input text that was entered by the user + */ + public static void execute(OrderStatistics stats, String inputText) { + String[] indexString = Parser.splitInput(Parser.analyzeInput(inputText), inputText); + double cost = Double.parseDouble(indexString[0]); + System.out.printf("Profit: $%.2f\n", stats.getProfit(cost)); + } +} diff --git a/src/main/java/command/stats/StatsTotalOrdersCommand.java b/src/main/java/command/stats/StatsTotalOrdersCommand.java new file mode 100644 index 0000000000..58e59d381c --- /dev/null +++ b/src/main/java/command/stats/StatsTotalOrdersCommand.java @@ -0,0 +1,14 @@ +package command.stats; + +import stats.OrderStatistics; + +public class StatsTotalOrdersCommand implements StatsCommand { + /** + * Executes the command to view the total number of orders. + * + * @param orderStatistics the statistics object containing the data + */ + public static void execute(OrderStatistics orderStatistics) { + System.out.println("Total number of orders: " + orderStatistics.getOrderCount()); + } +} diff --git a/src/main/java/logic/MainLogic.java b/src/main/java/logic/MainLogic.java new file mode 100644 index 0000000000..9c2883892b --- /dev/null +++ b/src/main/java/logic/MainLogic.java @@ -0,0 +1,159 @@ +package logic; + +import command.main.MainExitCommand; +import command.main.MainHelpCommand; +import command.main.MainCreateOrderCommand; +import command.main.MainViewOrderCommand; +import command.main.MainViewOrdersSummaryCommand; +import command.main.MainReceiptOrderCommand; +import command.main.MainEditMenuCommand; +import command.main.MainViewMenuCommand; +import command.main.MainViewMenusSummaryCommand; +import command.main.MainViewRestaurantInfoCommand; +import command.main.MainEditRestaurantInfoCommand; + + +import model.Menu; +import model.Order; +import model.Restaurant; +import storage.Storage; +import ui.CommandErrorMessage; +import ui.CommandType; +import ui.Parser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Optional; +import java.util.Scanner; + +public class MainLogic { + private static final int MAX_NAME_LENGTH = 50; + private static String userName; + + public static void main(String[] args) { + // Set default locale to US to ensure consistent formatting + Locale.setDefault(Locale.US); + + //Initialise all required models + System.out.println("Hello from DinEz"); + Scanner input = new Scanner(System.in); + Restaurant restaurant = new Restaurant(); + ArrayList ordersList = new ArrayList<>(); + ArrayList menusList = new ArrayList<>(); + + boolean isRestaurantLoaded = false; + try { + isRestaurantLoaded = Storage.checkRestaurantData(restaurant); + } catch (IOException | SecurityException e) { + System.out.println("Error creating save files, data might not be saved."); + } + + if (!isRestaurantLoaded) { + restaurant.initRestaurant(); + Storage.saveRestaurant(restaurant); + } + Storage.loadData(ordersList, menusList); + + boolean isValidUser = false; + + while (!isValidUser) { + isValidUser = initializeUser(); + } + + MainHelpCommand.execute(); + boolean isExit = false; + while (!isExit) { + System.out.print("[Main interface] >>> "); + String inputText = input.nextLine(); + CommandType commandType; + try { + commandType = Parser.analyzeInput(inputText); + } catch (IllegalArgumentException e) { + CommandErrorMessage.printMainError(inputText); + continue; + } + switch (commandType) { + case EXIT_MAIN: + isExit = MainExitCommand.execute(isExit); + assert isExit : "isExit should be true"; + break; + case HELP: + MainHelpCommand.execute(); + break; + case CREATE_ORDER: + //GOTO sub-menu to add/remove menuItems, inputText is passed to detect menu selected + Optional menuSelected = MainCreateOrderCommand.execute(inputText, menusList); + menuSelected.flatMap(menu -> OrderLogic.createNewOrder(input, menu, + restaurant.getRestaurantName(), restaurant.getRestaurantAddress(), userName)) + .ifPresent(order -> { + ordersList.add(order); + Storage.saveOrder(order); + }); + break; + case VIEW_ORDER: + MainViewOrderCommand.execute(ordersList, inputText); + break; + case VIEW_ALL_ORDERS: + MainViewOrdersSummaryCommand.execute(ordersList); + break; + case ORDER_RECEIPT: + MainReceiptOrderCommand.execute(ordersList, inputText); + break; + case CREATE_MENU: + MenuLogic.modifyMenu(input, null, menusList.toArray().length) + .ifPresentOrElse(menu -> { + menusList.add(menu); + Storage.saveMenu(menu); + }, () -> System.out.println("Menu not created")); + break; + case EDIT_MENU: + MainEditMenuCommand.execute(input, inputText, menusList); + break; + case VIEW_MENU: + MainViewMenuCommand.execute(menusList, inputText); + break; + case VIEW_ALL_MENUS: + MainViewMenusSummaryCommand.execute(menusList); + break; + case EDIT_RESTAURANT_INFO: + MainEditRestaurantInfoCommand.execute(restaurant); + break; + case VIEW_RESTAURANT_INFO: + MainViewRestaurantInfoCommand.execute(restaurant); + break; + case VIEW_PERFORMANCE_INFO: + StatsLogic.showStats(input, ordersList); + break; + default: + CommandErrorMessage.printMainError(inputText); + } + } + } + + /** + * Upon entering the system, the user is prompted to enter details about the restaurant including + * details like restaurant name, restaurant address as well as username. This function handles the + * prompting for input and receiving the input from the user + * @return true if the input was valid, false otherwise + */ + private static boolean initializeUser() { + System.out.println("Enter user name: "); + + Scanner input = new Scanner(System.in); + String inputString= input.nextLine(); + int length = inputString.length(); + if (length > MAX_NAME_LENGTH) { + System.out.println("Name is too long! Please enter a name with less than 50 characters."); + return false; + } + + if (inputString.isBlank() || inputString.isEmpty()) { + System.out.println("Input cannot be empty!"); + return false; + } else { + userName = inputString; + return true; + } + } +} diff --git a/src/main/java/logic/MenuLogic.java b/src/main/java/logic/MenuLogic.java new file mode 100644 index 0000000000..15d5bb8c44 --- /dev/null +++ b/src/main/java/logic/MenuLogic.java @@ -0,0 +1,66 @@ +package logic; + +import command.menu.MenuAddCommand; +import command.menu.MenuCompleteCommand; +import command.menu.MenuDeleteCommand; +import command.menu.MenuExitCommand; +import command.menu.MenuHelpCommand; +import model.Menu; +import ui.CommandErrorMessage; +import ui.CommandType; +import ui.Parser; + +import java.util.Optional; +import java.util.Scanner; + + +public class MenuLogic { + /** + * Allows the user to edit a menu that is specified + * @param input The scanner used to read input entered by the user from the command line + * @param menu A newly created copy of the menu that the user wants to edit + * @param menuLen The number of items in the menu + * @return The active menu (the menu the user is working with), otherwise return empty Optional + */ + public static Optional modifyMenu(Scanner input, Menu menu, int menuLen) { + Menu activeMenu = (menu == null) ? new Menu("" + (menuLen + 1)) : menu; + String menuID = activeMenu.getId(); + boolean isComplete = false; + System.out.println("Initializing menu " + menuID + "..."); + MenuHelpCommand.execute(); + while (!isComplete) { + System.out.print("[Menu: " + menuID + "] >>> "); + String inputText = input.nextLine(); + CommandType commandType; + try { + commandType = Parser.analyzeInput(inputText); + } catch (IllegalArgumentException e) { + CommandErrorMessage.printMenuError(inputText); + continue; + } + switch(commandType) { + case ADD_MENU_ITEM: + MenuAddCommand.execute(activeMenu, inputText); + break; + case DELETE_MENU_ITEM: + MenuDeleteCommand.execute(activeMenu, inputText); + break; + case VIEW_ITEMS: + System.out.println(activeMenu); + break; + case COMPLETE: + isComplete = MenuCompleteCommand.execute(activeMenu); + break; + case HELP: + MenuHelpCommand.execute(); + break; + case EXIT: + MenuExitCommand.execute(activeMenu); + return Optional.empty(); + default: + CommandErrorMessage.printMenuError(inputText); + } + } + return Optional.of(activeMenu); + } +} diff --git a/src/main/java/logic/OrderLogic.java b/src/main/java/logic/OrderLogic.java new file mode 100644 index 0000000000..9ea15f1ca4 --- /dev/null +++ b/src/main/java/logic/OrderLogic.java @@ -0,0 +1,97 @@ +package logic; + +import command.order.OrderViewItemsCommand; +import command.order.OrderAddCommand; +import command.order.OrderCompleteCommand; +import command.order.OrderDeleteCommand; +import command.order.OrderExitCommand; +import command.order.OrderHelpCommand; +import command.order.OrderViewMenuCommand; + +import model.Menu; +import model.Order; +import ui.CommandErrorMessage; +import ui.CommandType; +import ui.Parser; + +import java.util.Optional; +import java.util.Scanner; + +public class OrderLogic { + private static String orderType; + public static Optional createNewOrder(Scanner input, Menu menu, String restaurantName, + String restaurantAddress, String userName) { + boolean isValidOrderType = false; + while (!isValidOrderType) { + isValidOrderType = initializeOrderType(); + } + + Order newOrder = new Order(restaurantName,restaurantAddress,userName,orderType); + boolean isComplete = false; + String orderID = newOrder.getId(); + + System.out.println("Order " + orderID + " creating..."); + OrderHelpCommand.execute(); + while (!isComplete) { + System.out.print("[Order: " + orderID + "] [Menu: " + menu.getId() + "] >>> "); + String inputText = input.nextLine(); + CommandType commandType; + try { + commandType = Parser.analyzeInput(inputText); + } catch (IllegalArgumentException e) { + CommandErrorMessage.printOrderError(inputText); + continue; + } + switch (commandType) { + case ADD_ITEM: + OrderAddCommand.execute(newOrder, inputText, menu); + break; + case DELETE_ITEM: + OrderDeleteCommand.execute(newOrder, inputText, menu); + break; + case VIEW_ITEMS: + OrderViewItemsCommand.execute(newOrder); + break; + case VIEW_MENU_ORDERLOGIC: + OrderViewMenuCommand.execute(menu); + break; + case HELP: + OrderHelpCommand.execute(); + break; + case COMPLETE: + isComplete = OrderCompleteCommand.execute(newOrder, input, inputText); + break; + case EXIT: + OrderExitCommand.execute(newOrder); + return Optional.empty(); + default: + CommandErrorMessage.printOrderError(inputText); + } + } + return Optional.of(newOrder); + } + + /** + * Initializes the order type. + * + * @return true if the order type is successfully initialized, false otherwise + */ + private static boolean initializeOrderType() { + System.out.println("Would you like your order to be\n " + + " 1) dine in\n" + + " 2) takeaway\n" + + "Please enter 1 or 2: "); + Scanner orderTypeInput= new Scanner(System.in); + String orderTypeUserInput = orderTypeInput.nextLine(); + + if (orderTypeUserInput.trim().equals("1")) { + orderType = "Dine in"; + } else if (orderTypeUserInput.trim().equals("2")) { + orderType = "Takeaway"; + } else { + System.out.println("Input not 1 or 2!"); + return false; + } + return true; + } +} diff --git a/src/main/java/logic/StatsLogic.java b/src/main/java/logic/StatsLogic.java new file mode 100644 index 0000000000..3fbe3f5ea5 --- /dev/null +++ b/src/main/java/logic/StatsLogic.java @@ -0,0 +1,65 @@ +package logic; + +import command.stats.StatsBestSellingItemCommand; +import command.stats.StatsGrossRevenueCommand; +import command.stats.StatsNetRevenueCommand; +import command.stats.StatsTotalOrdersCommand; +import command.stats.StatsProfitCommand; +import command.stats.StatsHelpCommand; +import model.Order; +import stats.OrderStatistics; +import ui.CommandType; +import ui.Parser; + +import java.util.ArrayList; +import java.util.Scanner; + +public class StatsLogic { + public static void showStats(Scanner input, ArrayList orders) { + System.out.println("Gathering statistics..."); + StatsHelpCommand.execute(); + + OrderStatistics orderStatistics = new OrderStatistics(orders); + boolean hasQuit = false; + + while (!hasQuit) { + System.out.print("[Stats] >>> "); + String inputText = input.nextLine(); + CommandType commandType; + + try { + commandType = Parser.analyzeInput(inputText); + } catch (IllegalArgumentException e) { + System.out.println("Invalid command. Please try again."); + continue; + } + + switch (commandType) { + case VIEW_BEST_SELLING_ITEM: + StatsBestSellingItemCommand.execute(orderStatistics); + break; + case VIEW_GROSS_REVENUE: + StatsGrossRevenueCommand.execute(orderStatistics); + break; + case VIEW_NET_REVENUE: + StatsNetRevenueCommand.execute(orderStatistics); + break; + case VIEW_TOTAL_ORDERS: + StatsTotalOrdersCommand.execute(orderStatistics); + break; + case VIEW_PROFIT: + StatsProfitCommand.execute(orderStatistics, inputText); + break; + case QUIT: + hasQuit = true; + break; + case HELP: + StatsHelpCommand.execute(); + break; + default: + System.out.println("Invalid command. Please try again."); + break; + } + } + } +} diff --git a/src/main/java/model/Item.java b/src/main/java/model/Item.java new file mode 100644 index 0000000000..1a46413bcd --- /dev/null +++ b/src/main/java/model/Item.java @@ -0,0 +1,29 @@ +package model; + +public abstract class Item { + private String id; + private final String name; + private final double unitPrice; + + public Item(String id, String name, double unitPrice) { + this.id = id; + this.name = name; + this.unitPrice = unitPrice; + } + + public String getID() { + return this.id; + } + + public void setID(String id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public double getPrice() { + return this.unitPrice; + } +} diff --git a/src/main/java/model/ItemManager.java b/src/main/java/model/ItemManager.java new file mode 100644 index 0000000000..8fe90bb573 --- /dev/null +++ b/src/main/java/model/ItemManager.java @@ -0,0 +1,9 @@ +package model; + +public interface ItemManager { + + boolean add(MenuItem item); + boolean remove(String itemID); + + public String getId(); +} diff --git a/src/main/java/model/Menu.java b/src/main/java/model/Menu.java new file mode 100644 index 0000000000..31ee602754 --- /dev/null +++ b/src/main/java/model/Menu.java @@ -0,0 +1,132 @@ +package model; + +import java.util.ArrayList; + +import java.util.Optional; + + +public class Menu implements ItemManager { + private static final int NAME_MAX_LENGTH = 22; + private static final String ROW_DELIMITER = "+------+-----------------------------------+\n"; + private static final String MENU_HEADER = "+------------------------------------------+\n" + + "| MENU |\n" + + ROW_DELIMITER + + "| ID | Name | Price |\n" + + ROW_DELIMITER; + //private static final Logger logr = Logger.getLogger("MenuLogger"); + private final ArrayList menuItemList = new ArrayList<>(); + private final String menuID; + + public Menu(String menuID) { + //Menu.setupLogger(); + this.menuID = menuID; + } + + public Optional getItemById(String itemID) { + return menuItemList.stream().filter(x -> x.getID().equals(itemID)).findAny(); + } + + public Optional getItemByName(String itemName) { + return menuItemList.stream().filter(x -> x.getName().compareToIgnoreCase(itemName) == 0).findAny(); + } + + public ArrayList getMenuItemList() { + return menuItemList; + } + + @Override + public String getId() { + return menuID; + } + + @Override + public boolean add(MenuItem item) { + this.menuItemList.add(item); + return true; + } + + /** + * Removes item from the menu by its name + * @param itemID The name of the item + */ + @Override + public boolean remove(String itemID) { + assert itemID != null: "itemID of item to be removed should not be null"; + this.menuItemList.removeIf(x -> x.getID().equals(itemID)); + sortId(itemID); + return true; + } + + /** + * Sorts the id of the items in the menu after an item is removed + * @param startId The id of the item that is removed + */ + private void sortId(String startId) { + for (int id = Integer.parseInt(startId); id <= menuItemList.size(); id++) { + Item item = menuItemList.get(id - 1); + item.setID(id + ""); + } + } + + public int getSize() { + return menuItemList.size(); + } + + public String getMenuSummary() { + return String.format("Menu ID: %s, Number of items: %d", this.menuID, this.menuItemList.size()); + } + + @Override + public String toString() { + StringBuilder menuString = new StringBuilder(); + menuString.append(MENU_HEADER); + if (menuItemList.isEmpty()) { + menuString.append(ROW_DELIMITER); + } else { + for (MenuItem item : menuItemList) { + String shortName = item.getName().length() > NAME_MAX_LENGTH + ? item.getName().substring(0, NAME_MAX_LENGTH) : item.getName(); + + String priceFormat = (String.valueOf(item.getPrice()).length() > 7) ? + "| %-5s| %-22s|$%-10.2e|\n" : "| %-5s| %-22s| $%-9.2f|\n"; + + menuString.append(String.format(priceFormat, + item.getID(), shortName, item.getPrice())); + + // wrap the item name to the next line if it is too long + for (int i = NAME_MAX_LENGTH; i < item.getName().length(); i += NAME_MAX_LENGTH) { + String name = item.getName().substring(i, Math.min(i + NAME_MAX_LENGTH, item.getName().length())); + menuString.append(String.format("| %-5s| %-22s| %-10s|\n", + "", name, "")); + } + + } + menuString.append(ROW_DELIMITER); + } + + return menuString.toString(); + } + + /* + * Set up logger for this class. It has two handlers, one FileHandler and one ConsoleHandler + * FileHandler records log messages from FINE and above + * ConsoleHandler only records SEVERE messages + + + private static void setupLogger() { + LogManager.getLogManager().reset(); + logr.setLevel(Level.ALL); + + ConsoleHandler ch = new ConsoleHandler(); + ch.setLevel(Level.SEVERE); + logr.addHandler(ch); + + try { + FileHandler fh = new FileHandler("MenuLogger.log"); + fh.setLevel(Level.FINE); + logr.addHandler(fh); + } catch (java.io.IOException e) { + logr.log(Level.SEVERE, "File logger not working.", e); + } + }*/ +} diff --git a/src/main/java/model/MenuItem.java b/src/main/java/model/MenuItem.java new file mode 100644 index 0000000000..102d586765 --- /dev/null +++ b/src/main/java/model/MenuItem.java @@ -0,0 +1,18 @@ +package model; + + +public class MenuItem extends Item implements Comparable { + public MenuItem(String id, String name, double unitPrice) { + super(id, name, unitPrice); + } + + @Override + public String toString() { + return String.format("%s %s $%.2f", this.getID(), this.getName(), this.getPrice()); + } + + @Override + public int compareTo(MenuItem o) { + return this.getID().compareTo(o.getID()); + } +} diff --git a/src/main/java/model/Order.java b/src/main/java/model/Order.java new file mode 100644 index 0000000000..646e85b2a8 --- /dev/null +++ b/src/main/java/model/Order.java @@ -0,0 +1,383 @@ +package model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class Order implements ItemManager { + private static final double SERVICE_CHARGE = 0.1; + private static final double GST = 0.09; + + private static final String ROW_DELIMITER = "+-----------------------------------------------------+\n"; + private static final String TITLE = "RECEIPT"; + private static final String HEADERS = "| Item ID | Name | Unit Price | Quantity |\n"; + private static final int MAX_ITEM_LENGTH = 15; // Default length for Name column header + private static final String SHORT_ROW_START = "| %-8s | %-15s | $%-10.2"; + private static final String SHORT_ROW_END = " | %-9d|\n"; + private static final String LONG_ROW_FORMAT = "| | %-15s |" + + " ".repeat(13) + "|" + " ".repeat(10) + "|\n"; + private static final char SCIENTIFIC_NOTATION = 'e'; + private static final char FLOAT_NOTATION = 'f'; + + private static final int SUBTITLE_OFFSET = 2; + private static final int ORDER_TYPE_OFFSET = 16; + private static final int CASHIER_NAME_OFFSET = 13; + private static final int ORDER_ID_OFFSET = 14; + private static final int MAX_CHARGE_LENGTH = 6; + private static final int CHARGE_VALUE_OFFSET = 3; + private static final int MAX_TITLE_LENGTH = 50; + private String orderID; + private final String restaurantName; + private final String restaurantAddress; + private final String orderType; + private final String userName; + private final ArrayList orderItemList = new ArrayList<>(); + private double discount = 0; + + public Order(String restaurantName, String restaurantAddress, String userName, String orderType) { + this.orderID = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + this.restaurantName = restaurantName; + this.restaurantAddress = restaurantAddress; + this.userName = userName; + this.orderType = orderType; + } + + @Override + public boolean add(MenuItem item) throws IllegalArgumentException { + if (getItemCount(item.getID()) >= 9999) { + throw new IllegalArgumentException("The quantity of the item has reached the maximum of 9999"); + } + this.orderItemList.add(item); + return true; + } + + /** + * Removes all items from the order list by its id + * + * @param itemID the id of the item to be removed + */ + @Override + public boolean remove(String itemID) { + return this.orderItemList.removeIf(x -> x.getID().equals(itemID)); + } + + public boolean remove(MenuItem item) { + return this.orderItemList.remove(item); + } + + public int getItemCount(String itemID) { + return (int) this.orderItemList.stream().filter(x -> x.getID().equals(itemID)).count(); + } + + @Override + public String getId() { + return orderID; + } + + public int getSize() { + return this.orderItemList.size(); + } + + public double getTotalPrice() { + return Double.parseDouble(String.format("%.2f", + this.orderItemList.stream().mapToDouble(Item::getPrice).sum() * (1 + SERVICE_CHARGE) * (1 + GST))); + } + + /** + * Returns a formatted receipt header (top part of the receipt) + * + * @param headerBuilder the StringBuilder to append the header to + * @return the formatted receipt header + */ + private StringBuilder getReceiptHeader(StringBuilder headerBuilder) { + headerBuilder.append(ROW_DELIMITER) + .append("|") + .append(centerAlign(TITLE)) + .append("|\n") + .append(ROW_DELIMITER); + + wrapLongLine(restaurantName, headerBuilder); + wrapLongLine(restaurantAddress, headerBuilder); + + return headerBuilder + .append("|") + .append(centerAlign("")) + .append("|\n") + + .append("| Order Type: ") + .append(orderType) + .append(" ".repeat(ROW_DELIMITER.length() - orderType.length() - ORDER_TYPE_OFFSET)) + .append("|\n") + + .append("| Order ID: ") + .append(orderID) + .append(" ".repeat(ROW_DELIMITER.length() - orderID.length() - ORDER_ID_OFFSET)) + .append("|\n") + + .append(ROW_DELIMITER) + .append(HEADERS) + .append(ROW_DELIMITER); + } + + private void wrapLongLine(String longName, StringBuilder headerBuilder) { + for (int i = 0; i < longName.length(); i += MAX_TITLE_LENGTH) { + String partialName = longName + .substring(i, Math.min(i + MAX_TITLE_LENGTH, longName.length())); + + headerBuilder.append("|") + .append(centerAlign(partialName)) + .append("|\n"); + } + } + + private String centerAlign(String word) { + int totalSpaces = ROW_DELIMITER.length() - word.length() - SUBTITLE_OFFSET; + int leftSpaces = totalSpaces / 2; + int rightSpaces = leftSpaces; + + if (totalSpaces % 2 == 0) { // to offset the difference in left and right for odd length + rightSpaces -= 1; + } + return " ".repeat(leftSpaces) + word + " ".repeat(rightSpaces); + } + + /** + * Returns a formatted receipt with summary (bottom part of the receipt) appended + * + * @param receiptBuilder the StringBuilder to append the summary to + * @param discount the discount to be applied to the order + * @return the formatted receipt with summary + */ + private StringBuilder getReceiptSummary(StringBuilder receiptBuilder, double discount) { + double netTotal = getTotalPrice() / (1 + GST) / (1 + SERVICE_CHARGE) * (1 - discount); + double discountAmount = -discount * netTotal / (1 - discount); + double serviceCharge = netTotal * SERVICE_CHARGE; + double gst = netTotal * (1 + SERVICE_CHARGE) * GST; + double grandTotal = netTotal + serviceCharge + gst; + int maxLength = 0; + + // find which number has the most number of digits + for (double num : new double[]{discountAmount, netTotal, serviceCharge, gst, grandTotal}) { + int numLength = String.valueOf((int) Math.abs(num)).length() + CHARGE_VALUE_OFFSET; + + if (numLength > 9) { + String scientificNotation = String.format("%.2e", Math.abs(num)); + numLength = scientificNotation.length(); + } + if (numLength >= maxLength) { + maxLength = numLength; + } + } + receiptBuilder.append(ROW_DELIMITER); + + if (discount != 0) { + receiptBuilder + .append(setChargeFormat((int) (discount * 100) + "% Off Discount:", discountAmount, maxLength)); + } + + receiptBuilder + .append(setChargeFormat("Subtotal:", netTotal, maxLength)) + .append(ROW_DELIMITER) + .append(setChargeFormat(setChargeDescription("Service Charge", SERVICE_CHARGE) + , serviceCharge, maxLength)) + .append(setChargeFormat(setChargeDescription("GST", GST), gst, maxLength)) + .append(setChargeFormat("Grand Total:", grandTotal, maxLength)) + .append(ROW_DELIMITER); + + return receiptBuilder.append("| Cashier: ") + .append(userName) + .append(" ".repeat(ROW_DELIMITER.length() - userName.length()- CASHIER_NAME_OFFSET)) + .append("|\n") + .append(ROW_DELIMITER); + } + + /** + * Returns a formatted charge description with the percentage in brackets + * + * @param description the description of the charge + * @param percentage the value of the charge in decimal form e.g. 0.35 for 35% discount + * @return the formatted charge description + */ + private String setChargeDescription(String description, double percentage) { + return String.format("%s (%.1f%%): ", description, percentage * 100); + } + + /** + * Returns a formatted charge line aligned to the right by the dollar sign + * + * @param description the description of the charge + * @param charge the charge to be displayed + * @param maxDigits the maximum number of digits in the charge + * @return the formatted charge line + */ + private String setChargeFormat(String description, double charge, int maxDigits) { + int descriptionLength = String.valueOf(description).length(); + int middleSpace = ROW_DELIMITER.length() - descriptionLength - maxDigits - 2 * CHARGE_VALUE_OFFSET - 1; + StringBuilder chargeLine = new StringBuilder().append("| ").append(description); + int i = 0; + + while (i < middleSpace) { + chargeLine.append(" "); + i++; + } + + if (charge < 0) { + chargeLine.append("-"); + charge = -charge; + } else { + chargeLine.append(" "); + } + + chargeLine.append(String.format("$%.2" + chooseFormat(charge), charge)); + i = chargeLine.length(); + + while (i < ROW_DELIMITER.length() - 2) { + chargeLine.append(" "); + i++; + } + + chargeLine.append("|\n"); + return chargeLine.toString(); + } + + /** + * Returns a formatted item list (middle part of the receipt) + * + * @param receiptBuilder the StringBuilder to append the item list to + */ + private void getItemList(StringBuilder receiptBuilder) { + Set processedItems = new HashSet<>(); + + for (MenuItem item : orderItemList) { + String itemID = item.getID(); + + if (!processedItems.contains(itemID)) { + int quantity = getItemCount(itemID); + String shortName = item.getName().length() > MAX_ITEM_LENGTH + ? item.getName().substring(0, MAX_ITEM_LENGTH) : item.getName(); + String shortNameFormat = SHORT_ROW_START + chooseFormat(item.getPrice()) + SHORT_ROW_END; + + receiptBuilder.append(String.format(shortNameFormat, itemID, shortName, item.getPrice(), quantity)); + + // iterate through the rest part of the name, keep the max length of 15 + for (int i = MAX_ITEM_LENGTH; i < item.getName().length(); i += MAX_ITEM_LENGTH) { + String partialName = item.getName() + .substring(i, Math.min(i + MAX_ITEM_LENGTH, item.getName().length())); + + receiptBuilder.append(String.format(LONG_ROW_FORMAT, partialName)); + } + processedItems.add(itemID); + } + } + } + + /** + * Returns a formatted and complete receipt + * + * @param discount the discount to be applied to the order in decimal form ranging from 0.1 to 0.99 + * in 2 decimal places, with 0 being an exception for no discount (e.g. 0.15 for 15% discount) + * @return the formatted receipt + * @throws IllegalArgumentException if the discount is not between 1% and 99% + */ + public String getReceipt(double discount) throws IllegalArgumentException { + if (discount != 0 && !(discount >= 0.01 && discount <= 0.99)) { + throw new NumberFormatException("Discount must be between 0 and 99 (inclusive)"); + } + setDiscount(discount); + StringBuilder receiptBuilder = new StringBuilder(); + + receiptBuilder = getReceiptHeader(receiptBuilder); + getItemList(receiptBuilder); + receiptBuilder = getReceiptSummary(receiptBuilder, discount); + + return receiptBuilder.toString(); + } + + /** + * Returns a formatted receipt without any discount + * + * @return the formatted receipt + */ + public String getReceipt() { + return getReceipt(0); + } + + /** + * Chooses the format of the number based on the length of the integer part + * + * @param value the value to be formatted + * @return 'e' if the value has more than 7 digits, 'f' otherwise + */ + private char chooseFormat(double value) { + // If the value is too large, use scientific notation + return (String.valueOf((int) (value)).length()) > MAX_CHARGE_LENGTH ? SCIENTIFIC_NOTATION : FLOAT_NOTATION; + } + + /** + * Returns a formatted item list with the current order + * + * @return the formatted table of items in the order + */ + public String viewItems() { + StringBuilder receiptBuilder = new StringBuilder(); + + receiptBuilder.append(ROW_DELIMITER) + .append("|") + .append(centerAlign("Current Order")) + .append("|\n").append(ROW_DELIMITER); + + getItemList(receiptBuilder); + + receiptBuilder = getReceiptSummary(receiptBuilder, 0); + return receiptBuilder.toString(); + } + + public String getRestaurantName() { + return restaurantName; + } + + public String getRestaurantAddress() { + return restaurantAddress; + } + + public String getUserName() { + return userName; + } + + public String getOrderType() { + return orderType; + } + + public ArrayList getOrderItemList() { + return orderItemList; + } + + public void setOrderID(String orderID) { + this.orderID = orderID; + } + + /** + * Returns a brief summary of the order + * + * @return the orderID and the total price of the order + */ + public String getOrderSummary() { + return this.orderID + " {Total Price: " + this.getTotalPrice() * (1 - this.discount) + "}"; + } + + private void setDiscount(double discount) { + this.discount = discount; + } + + @Override + public String toString() { + return this.orderID + "\n" + + IntStream.range(0, this.orderItemList.size()) + .mapToObj(x -> (x + 1) + ". " + this.orderItemList.get(x)) + .collect(Collectors.joining("\n")); + } +} diff --git a/src/main/java/model/Restaurant.java b/src/main/java/model/Restaurant.java new file mode 100644 index 0000000000..c08e71beee --- /dev/null +++ b/src/main/java/model/Restaurant.java @@ -0,0 +1,97 @@ +package model; + +import java.util.Scanner; + +public class Restaurant { + private String restaurantName; + private String restaurantAddress; + + //@@author Zhengwinter + /** + * Initializes the restaurant object by prompting the user for the restaurant name and address. + */ + public void initRestaurant() { + boolean isValidRestaurantName = false; + boolean isValidAddress = false; + while (!isValidRestaurantName) { + isValidRestaurantName = getInput("Restaurant"); + } + while (!isValidAddress) { + isValidAddress = getInput("Address"); + } + } + + /** + * Obtains a specific input from the user based on the provided token. + * + * @param token A String representing the value to be initialized (e.g., "Restaurant", "Address") + * @return true if the user input is valid, false otherwise + */ + private boolean getInput (String token) { + switch(token) { + case "Restaurant": + System.out.println("Enter restaurant name: "); + break; + case "Address": + System.out.println("Enter address of restaurant: "); + break; + default: + System.out.println("Error in received initialization token"); + } + Scanner input = new Scanner(System.in); + String inputString= input.nextLine(); + switch(token) { + case "Restaurant": + restaurantName = inputString; + break; + case "Address": + restaurantAddress = inputString; + break; + default: + System.out.println("Error in received initialization token"); + } + if (inputString.isBlank() || inputString.isEmpty()) { + System.out.println("Input cannot be empty!"); + return false; + } else { + return true; + } + } + + //@@author webtjs + /** + * Returns the name of the restaurant. + * + * @return The name of the restaurant + */ + public String getRestaurantName() { + return restaurantName; + } + + /** + * Sets the name of the restaurant. + * + * @param name The new name of the restaurant + */ + public void setRestaurantName(String name) { + restaurantName = name; + } + + /** + * Returns the address of the restaurant. + * + * @return The address of the restaurant + */ + public String getRestaurantAddress() { + return restaurantAddress; + } + + /** + * Sets the address of the restaurant. + * + * @param address The new address of the restaurant + */ + public void setRestaurantAddress(String address) { + restaurantAddress = address; + } +} diff --git a/src/main/java/stats/OrderStatistics.java b/src/main/java/stats/OrderStatistics.java new file mode 100644 index 0000000000..1a28d2ab9b --- /dev/null +++ b/src/main/java/stats/OrderStatistics.java @@ -0,0 +1,89 @@ +package stats; + +import model.Item; +import model.MenuItem; +import model.Order; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class OrderStatistics { + private final ArrayList orders; + private final ArrayList itemList = new ArrayList<>(); + private final List> itemFrequencies = new ArrayList<>(); + private final double grossSales; + + public OrderStatistics(ArrayList orders) { + this.orders = orders; + this.grossSales = getGrossRevenue(); + + for (Order order : orders) { + itemList.addAll(order.getOrderItemList()); + } + + itemList.sort(MenuItem::compareTo); + setItemFrequencies(); + } + + private void setItemFrequencies() { + for (MenuItem item : itemList) { + Pair newPair = new Pair<>(item, 1); + + for (Pair pair : itemFrequencies) { + if (pair.getFirst().compareTo(item) == 0) { + // If the item is already in the list, increment its frequency + newPair = new Pair<>(pair.getFirst(), pair.getSecond() + 1); + itemFrequencies.removeIf(oldPair -> oldPair.getFirst().compareTo(item) == 0); + break; + } + } + + itemFrequencies.add(newPair); + } + } + + private void sortFrequenciesDescending() { + itemFrequencies.sort((item1, item2) -> item2.getSecond() - item1.getSecond()); + } + + public List getBestSellingItems() { + sortFrequenciesDescending(); + List bestSellingItems = new ArrayList<>(); + + if (itemFrequencies.isEmpty()) { + return bestSellingItems; + } + + // check if there are multiple best-selling items, + for (Pair itemFrequency : itemFrequencies) { + if (Objects.equals(itemFrequency.getSecond(), itemFrequencies.get(0).getSecond())) { + bestSellingItems.add(itemFrequency.getFirst()); + } else { + break; + } + } + + return bestSellingItems; + } + + public double getGrossRevenue() { + double totalSales = 0.0; + for (Order order : orders) { + totalSales += order.getTotalPrice(); + } + return totalSales; + } + + public double getNetRevenue() { + return grossSales / (1 + 0.09); + } + + public double getProfit(double cost) { + return getNetRevenue() - cost; + } + + public int getOrderCount() { + return orders.size(); + } +} diff --git a/src/main/java/stats/Pair.java b/src/main/java/stats/Pair.java new file mode 100644 index 0000000000..7150b2344e --- /dev/null +++ b/src/main/java/stats/Pair.java @@ -0,0 +1,24 @@ +package stats; + +public class Pair { + private final T first; + private final U second; + + public Pair(T first, U second) { + this.first = first; + this.second = second; + } + + public T getFirst() { + return first; + } + + public U getSecond() { + return second; + } + + @Override + public String toString() { + return "(" + first + ", " + second + ")"; + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..98c845b90b --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,247 @@ +package storage; + +import model.Menu; +import model.MenuItem; +import model.Order; +import model.Restaurant; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Scanner; +import java.util.Set; +import java.util.NoSuchElementException; + +public class Storage { + private static final String RESTAURANT_FILEPATH = "data/restaurant.txt"; + private static final String ORDERS_FILEPATH = "data/orders.txt"; + private static final String MENUS_FILEPATH = "data/menus.txt"; + private static final String SEPARATOR = " | "; + + /** + * Checks if a new restaurant needs to be created based on the provided restaurant data. + * + * @param restaurant The Restaurant object to check + * @return true if a new restaurant needs to be created, false otherwise + * @throws IOException If an I/O error occurs while creating or reading the restaurant file + */ + public static boolean checkRestaurantData(Restaurant restaurant) throws IOException{ + File restaurantFile = new File(RESTAURANT_FILEPATH); + restaurantFile.getParentFile().mkdirs(); + restaurantFile.createNewFile(); + try { + loadRestaurant(restaurant); + return true; + } catch (ArrayIndexOutOfBoundsException | FileNotFoundException | NoSuchElementException e) { + return false; + } + } + + /** + * Loads the order and menu data into their respective ArrayLists. + * + * @param ordersList The ArrayList to store the loaded orders + * @param menusList The ArrayList to store the loaded menus + */ + public static void loadData(ArrayList ordersList, ArrayList menusList) { + try { + loadOrders(ordersList); + loadMenus(menusList); + } catch (IOException e) { + System.out.println("Error loading order data."); + } + } + + /** + * Loads the restaurant data from the restaurant save file and sets the name and address + * in the provided Restaurant object. + * + * @param restaurant The Restaurant object to populate with data + * @throws FileNotFoundException If the restaurant save file is not found + */ + private static void loadRestaurant(Restaurant restaurant) throws FileNotFoundException { + File restaurantFile = new File(RESTAURANT_FILEPATH); + Scanner restaurantScanner = new Scanner(restaurantFile); + String[] restaurantDetails = restaurantScanner.nextLine().split(" \\| "); + restaurant.setRestaurantName(restaurantDetails[0]); + restaurant.setRestaurantAddress(restaurantDetails[1]); + } + + /** + * Loads the order data from the order file and populates the provided ArrayList + * with the loaded orders. + * + * @param ordersList The ArrayList to store the loaded orders + * @throws IOException If an I/O error occurs while reading the order file + */ + private static void loadOrders(ArrayList ordersList) throws IOException{ + File ordersFile = new File(ORDERS_FILEPATH); + if (ordersFile.createNewFile()) { + return; + } + Scanner ordersScanner = new Scanner(ordersFile); + while (ordersScanner.hasNext()) { + try { + String[] restaurantDetails = ordersScanner.nextLine().split(" \\| "); + String[] orderDetails = ordersScanner.nextLine().split(" \\| "); + String orderType = orderDetails[2]; + if (!orderType.equals("Takeaway") && !orderType.equals("Dine in")) { + throw new IllegalArgumentException("Invalid order type"); + } + Order order = new Order(restaurantDetails[0], restaurantDetails[1], orderDetails[1], orderType); + order.setOrderID(orderDetails[0]); + while (ordersScanner.hasNext()) { + String line = ordersScanner.nextLine(); + if (line.trim().equals("-")) { + break; + } + String[] itemDetails = line.split(" \\| "); + int itemID = Integer.parseInt(itemDetails[0]); + double itemPrice = Double.parseDouble(itemDetails[2]); + if (itemID <= 0 || itemPrice <= 0) { + throw new IllegalArgumentException("Negative number received"); + } + MenuItem item = new MenuItem(itemDetails[0], itemDetails[1], itemPrice); + for (int i = 0; i < Integer.parseInt(itemDetails[3]); i++) { + order.add(item); + } + } + ordersList.add(order); + } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException | NullPointerException e) { + ordersScanner.close(); + System.out.println("Order data corrupted, erasing data..."); + ordersFile.delete(); + return; + } + + } + } + + /** + * Loads the menu data from the menu file and populates the provided ArrayList + * with the loaded menus. + * + * @param menusList The ArrayList to store the loaded menus + * @throws IOException If an I/O error occurs while reading the menu file + */ + private static void loadMenus(ArrayList menusList) throws IOException{ + File menusFile = new File(MENUS_FILEPATH); + if (menusFile.createNewFile()) { + return; + } + Scanner menusScanner = new Scanner(menusFile); + while (menusScanner.hasNext()) { + String menuID = menusScanner.nextLine().trim(); + Menu menu = new Menu(menuID); + while (menusScanner.hasNext()) { + String line = menusScanner.nextLine(); + if (line.trim().equals("-")) { + break; + } + String[] itemDetails = line.split(" \\| "); + try { + int itemID = Integer.parseInt(itemDetails[0]); + double itemPrice = Double.parseDouble(itemDetails[2]); + if (itemID <= 0 || itemPrice <= 0) { + throw new IllegalArgumentException("Negative number received"); + } + MenuItem item = new MenuItem(itemDetails[0], itemDetails[1], itemPrice); + menu.add(item); + } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException | NullPointerException e) { + menusScanner.close(); + System.out.println("Menu data corrupted, erasing data..."); + menusFile.delete(); + return; + } + } + menusList.add(menu); + } + } + + /** + * Saves restaurant information to the restaurant save file. + * + * @param restaurant The Restaurant object with information to save + */ + public static void saveRestaurant(Restaurant restaurant) { + try { + FileWriter fw = new FileWriter(RESTAURANT_FILEPATH); + String restaurantDetails = restaurant.getRestaurantName() + SEPARATOR + restaurant.getRestaurantAddress(); + fw.write(restaurantDetails); + fw.close(); + } catch (IOException e) { + System.out.println("Error saving restaurant data."); + } + } + + /** + * Saves order information to the order save file. + * + * @param order The Order object with information to save + */ + public static void saveOrder(Order order){ + try { + FileWriter fw = new FileWriter(ORDERS_FILEPATH, true); + String restaurantDetails = order.getRestaurantName() + SEPARATOR + order.getRestaurantAddress() + "\n"; + String orderDetails = order.getId() + SEPARATOR + order.getUserName() + + SEPARATOR + order.getOrderType() + "\n"; + fw.write(restaurantDetails); + fw.write(orderDetails); + + Set orderItems = new HashSet<>(); + for (MenuItem item : order.getOrderItemList()) { + String itemID = item.getID(); + if (orderItems.contains(itemID)) { + continue; + } + int quantity = order.getItemCount(itemID); + String itemData = itemID + SEPARATOR + item.getName() + SEPARATOR + + item.getPrice() + SEPARATOR + quantity + "\n"; + fw.write(itemData); + orderItems.add(itemID); + } + fw.write("-\n"); + fw.close(); + } catch (IOException e) { + System.out.println("Error saving order data."); + } + } + + /** + * Saves menu information to the menu save file. + * + * @param menu The Menu object with information to save + */ + public static void saveMenu(Menu menu) { + try { + FileWriter fw = new FileWriter(MENUS_FILEPATH, true); + fw.append(menu.getId()).append("\n"); + + for (MenuItem item : menu.getMenuItemList()) { + String itemData = item.getID() + SEPARATOR + item.getName() + SEPARATOR + item.getPrice() + "\n"; + fw.write(itemData); + } + fw.write("-\n"); + fw.close(); + } catch (IOException e) { + System.out.println("Error saving menu data."); + } + } + + /** + * Updates the menu save file with the data from the provided list of menus. + * + * @param menusList The ArrayList of Menu objects to update in the menu file + * @throws IOException If an I/O error occurs while writing to the menu file + */ + public static void updateMenus(ArrayList menusList) throws IOException{ + FileWriter fw = new FileWriter(MENUS_FILEPATH); + for (Menu menu : menusList) { + saveMenu(menu); + } + fw.close(); + } +} diff --git a/src/main/java/ui/CommandErrorMessage.java b/src/main/java/ui/CommandErrorMessage.java new file mode 100644 index 0000000000..a13af51333 --- /dev/null +++ b/src/main/java/ui/CommandErrorMessage.java @@ -0,0 +1,46 @@ +package ui; + +public class CommandErrorMessage { + private static final String INVALID_PARAMS = + "Please ensure that you have entered the necessary parameters correctly."; + private static final String ORDER_VIEW = "Invalid format for view command. " + + "Please enter either \"view menus\" or \"view items\"."; + private static final String MENU_VIEW = "Invalid format for view command. Please enter \"view items\"."; + private static final String INVALID_COMMAND = + "Command not recognised. Type \"help\" to see the list of available commands."; + + public static void printMainError(String inputText) { + String errorMessage = ""; + if (inputText.startsWith("view") || inputText.startsWith("create") + || inputText.startsWith("receipt") || inputText.startsWith("edit")) { + errorMessage = INVALID_PARAMS; + } else { + errorMessage = INVALID_COMMAND; + } + System.out.println(errorMessage); + } + + public static void printOrderError(String inputText) { + String errorMessage = ""; + if (inputText.startsWith("add") || inputText.startsWith("delete")) { + errorMessage = INVALID_PARAMS; + } else if (inputText.startsWith("view")) { + errorMessage = ORDER_VIEW; + } else { + errorMessage = INVALID_COMMAND; + } + System.out.println(errorMessage); + } + + public static void printMenuError(String inputText) { + String errorMessage = ""; + if (inputText.startsWith("add") || inputText.startsWith("delete")) { + errorMessage = INVALID_PARAMS; + } else if (inputText.startsWith("view")) { + errorMessage = MENU_VIEW; + } else { + errorMessage = INVALID_COMMAND; + } + System.out.println(errorMessage); + } +} diff --git a/src/main/java/ui/CommandType.java b/src/main/java/ui/CommandType.java new file mode 100644 index 0000000000..67487740d1 --- /dev/null +++ b/src/main/java/ui/CommandType.java @@ -0,0 +1,49 @@ +package ui; + +public enum CommandType { + //main commands + EXIT_MAIN("(?i)bye"), + HELP("(?i)help"), + CREATE_ORDER("(?i)create\\s*order\\s*-menu\\s*(\\d+)"), // case-insensitive, space safe + VIEW_ORDER("(?i)view\\s*-order\\s*(\\d+)"), + VIEW_ALL_ORDERS("(?i)view\\s*-order\\s*-all"), + VIEW_MENU("(?i)view\\s*-menu\\s*(\\d+)"), + VIEW_ALL_MENUS("(?i)view\\s*-menu\\s*-all"), + CREATE_MENU("(?i)create\\s*menu"), + EDIT_MENU("(?i)edit\\s*-menu\\s*(\\d+)"), + ORDER_RECEIPT("(?i)receipt\\s*-order\\s*(\\d+)"), + VIEW_RESTAURANT_INFO("(?i)view\\s*restaurant"), + EDIT_RESTAURANT_INFO("(?i)edit\\s*restaurant"), + VIEW_PERFORMANCE_INFO("(?i)view\\s*performance"), + + + //order commands + ADD_ITEM("(?i)add\\s*-item\\s*(\\d+)\\s*-quantity\\s*(\\d+)"), + DELETE_ITEM("(?i)delete\\s*-item\\s*(\\d+)\\s*-quantity\\s*(\\d+)"), + VIEW_ITEMS("(?i)view\\s*items"), + COMPLETE("(?i)complete\\s*(-discount\\s*(\\d+))?"), + VIEW_MENU_ORDERLOGIC("(?i)view\\s*menu"), + + EXIT("(?i)cancel"), + + // edit menu commands + ADD_MENU_ITEM("(?i)add\\s*-item\\s*([A-Z\\s]+)\\s*-price\\s*(\\d+(\\.\\d+)?)"), + DELETE_MENU_ITEM("(?i)delete\\s*-item\\s*(\\d+)"), + + // stats commands + VIEW_BEST_SELLING_ITEM("(?i)bestselling"), + VIEW_TOTAL_ORDERS("(?i)total\\s*orders"), + VIEW_GROSS_REVENUE("(?i)revenue\\s*-gross"), + VIEW_NET_REVENUE("(?i)revenue\\s*-net"), + VIEW_PROFIT("(?i)view\\s*profit\\s*-cost\\s*(\\d+(\\.\\d+)?)"), + QUIT("(?i)quit"); + + private final String commandRegex; + CommandType(String commandRegex) { + this.commandRegex = commandRegex; + } + + String getCommandRegex() { + return commandRegex; + } +} diff --git a/src/main/java/ui/Parser.java b/src/main/java/ui/Parser.java new file mode 100644 index 0000000000..95063281f2 --- /dev/null +++ b/src/main/java/ui/Parser.java @@ -0,0 +1,62 @@ +package ui; + +import java.util.logging.Logger; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +public class Parser { + private static final Logger logger = Logger.getLogger(Parser.class.getName()); + private static final String ANALYZE_INPUT = "analyzeInput"; + private static final String SPLIT_INPUT = "splitInput"; + + private static final String ANALYZE_INITIALIZATION = "analyzeInitialization"; + + /** + * Analyzes the given input and returns the corresponding token. + * + * @param input The input to be analyzed. + * @return The corresponding token. + * @throws IllegalArgumentException If the input is invalid. + */ + public static CommandType analyzeInput(String input) throws IllegalArgumentException { + logger.entering(Parser.class.getName(), ANALYZE_INPUT, input); + + return Arrays.stream(CommandType.values()) + .filter(token -> input.matches(token.getCommandRegex())) + .findFirst() + .orElseThrow(() -> { + logger.throwing(Parser.class.getName(), + ANALYZE_INPUT, new IllegalArgumentException("Invalid input")); + return new IllegalArgumentException("Invalid input"); + }); + } + + /** + * Splits the given input based on the given token. + * + * @param token The token to be used for splitting the input. + * @param input The input to be split. + * @return The split input containing the arguments required for the command. + */ + public static String[] splitInput(CommandType token, String input) { + logger.entering(Parser.class.getName(), + SPLIT_INPUT, new Object[]{token, input}); + + assert token != null : "Token cannot be null"; // Ensures command type token is not null + + Pattern matchedPattern = Pattern.compile(token.getCommandRegex()); + Matcher matcher = matchedPattern.matcher(input); + boolean hasMatched = matcher.matches(); + assert hasMatched : "Input does not match the token"; // Ensures input matches the token + + return IntStream.rangeClosed(1, matcher.groupCount()) + .mapToObj(matcher::group) + .filter(s -> s != null && !s.isEmpty()) + .map(String::trim) + .peek(s -> logger.fine("Split input: " + s)) + .toArray(String[]::new); + } +} diff --git a/src/test/java/command/MainCommandTest.java b/src/test/java/command/MainCommandTest.java new file mode 100644 index 0000000000..bc116ac9d7 --- /dev/null +++ b/src/test/java/command/MainCommandTest.java @@ -0,0 +1,281 @@ +package command; + +import command.main.MainHelpCommand; +import command.main.MainExitCommand; +import command.main.MainViewOrderCommand; +import command.main.MainViewOrdersSummaryCommand; +import command.main.MainCreateOrderCommand; +import command.main.MainEditMenuCommand; +import command.main.MainViewMenuCommand; +import command.main.MainViewRestaurantInfoCommand; +import command.main.MainViewMenusSummaryCommand; +import command.main.MainReceiptOrderCommand; +import model.Menu; +import model.MenuItem; +import model.Order; +import model.Restaurant; +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.Scanner; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MainCommandTest { + private Menu generateMenu() { + MenuItem dish1 = new MenuItem("1", "Burger", 6.00); + MenuItem dish2 = new MenuItem("2", "Fries", 3.00); + MenuItem dish3 = new MenuItem("3", "Sandwich", 4.00); + + Menu testMenu = new Menu("1"); + testMenu.add(dish1); + testMenu.add(dish2); + testMenu.add(dish3); + return testMenu; + } + + private Restaurant generateRestaurant() { + Restaurant restaurant = new Restaurant(); + restaurant.setRestaurantName("Pho King"); + restaurant.setRestaurantAddress("NUS"); + return restaurant; + } + + private Order generateOrder() { + MenuItem dish1 = new MenuItem("1", "Burger", 6.00); + MenuItem dish2 = new MenuItem("2", "Fries", 3.00); + MenuItem dish3 = new MenuItem("3", "Sandwich", 4.00); + Restaurant testRestaurant = generateRestaurant(); + Order testOrder = new Order(testRestaurant.getRestaurantName(), testRestaurant.getRestaurantAddress(), + "John", "Takeaway"); + testOrder.setOrderID("12345"); + testOrder.add(dish1); + testOrder.add(dish2); + testOrder.add(dish3); + return testOrder; + } + + @Test + public void testMainHelpCommand() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + MainHelpCommand.execute(); + String expectedOutput = "Here are the list of available commands:\n" + + "\thelp: Shows all the commands that can be used.\n" + + "\tcreate order -menu : Creates a new order using the specified menu.\n" + + "\tview -order -all: Shows a brief summary of all the created orders.\n" + + "\tview -order : Shows all the contents of a specified order.\n" + + "\treceipt -order : shows the receipt of the specified order.\n" + + "\tcreate menu: Creates a new menu.\n" + + "\tedit -menu : Modify the specified menu's items in the menu interface.\n" + + "\tview -menu : Shows all the contents of a specified menu.\n" + + "\tview -menu -all: Shows a brief summary of all the created menus.\n" + + "\tedit restaurant: Modify restaurant name and address.\n" + + "\tview restaurant: Shows the restaurant name and address currently in use.\n" + + "\tview performance: Enters the order statistics interface.\n" + + "\tbye: Quits the program.\n"; + + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainExitCommand() { + assertTrue(MainExitCommand.execute(false)); + } + + + @Test + public void testMainViewOrdersSummaryCommand_emptyList() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + ArrayList emptyList = new ArrayList<>(); + MainViewOrdersSummaryCommand.execute(emptyList); + String expectedOutput = "No orders available\n"; + + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainViewOrderCommand_invalidOrder() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + ArrayList emptyList = new ArrayList<>(); + MainViewOrderCommand.execute(emptyList, "view -order 12345"); + String expectedOutput = "Order not found\n"; + + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainCreateOrderCommand_invalidMenu() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + Menu testMenu = new Menu("01"); + ArrayList testMenuList = new ArrayList<>(); + testMenuList.add(testMenu); + MainCreateOrderCommand.execute("create order -menu 02", testMenuList); + String expectedOutput = "Menu does not exist\n" + "Order not created\n"; + + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainEditMenuCommand_invalidMenu() { + ByteArrayInputStream inputContent = new ByteArrayInputStream("test".getBytes()); + Scanner input = new Scanner(inputContent); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + Menu testMenu = new Menu("1"); + ArrayList testMenuList = new ArrayList<>(); + testMenuList.add(testMenu); + MainEditMenuCommand.execute(input, "edit -menu 2", testMenuList); + String expectedOutput = "Menu ID not found\n"; + + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainEditMenuCommand_validMenu() { + String commands = "add -item Pizza -price 6\n" + "complete\n"; + ByteArrayInputStream inputContent = new ByteArrayInputStream(commands.getBytes()); + Scanner input = new Scanner(inputContent); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + ArrayList testMenuList = new ArrayList<>(); + testMenuList.add(generateMenu()); + MainEditMenuCommand.execute(input, "edit -menu 1", testMenuList); + String expectedOutput = "Initializing menu 1...\n" + + "Here are the list of available commands:\n" + + "\thelp: Shows all the commands that can be used.\n" + + "\tadd -item -price : Adds a new item to the menu.\n" + + "\tdelete -item : Deletes an item from the menu.\n" + + "\tview items: Shows all the items in the current menu.\n" + + "\tcomplete: Completes the menu and returns to the main menu.\n" + + "\tcancel: Aborts the current menu and returns to the main menu.\n" + + "[Menu: 1] >>> Item successfully added to menu!\n" + + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Burger | $6.00 |\n" + + "| 2 | Fries | $3.00 |\n" + + "| 3 | Sandwich | $4.00 |\n" + + "| 4 | Pizza | $6.00 |\n" + + "+------+-----------------------------------+\n" + + "\n" + + "[Menu: 1] >>> Menu 1 has been saved!\n"; + + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainViewMenuCommand_validMenu() { + ArrayList testMenuList = new ArrayList<>(); + testMenuList.add(generateMenu()); + + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + MainViewMenuCommand.execute(testMenuList, "view -menu 1"); + String expectedOutput = "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Burger | $6.00 |\n" + + "| 2 | Fries | $3.00 |\n" + + "| 3 | Sandwich | $4.00 |\n" + + "+------+-----------------------------------+\n\n"; + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainViewCommand_invalidMenu() { + ArrayList testMenuList = new ArrayList<>(); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + MainViewMenuCommand.execute(testMenuList, "view -menu 1"); + String expectedOutput = "Menu not found\n"; + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainViewMenusSummaryCommand_emptyMenusList() { + ArrayList testMenuList = new ArrayList<>(); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + MainViewMenusSummaryCommand.execute(testMenuList); + String expectedOutput = "No menus available\n"; + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainViewRestaurantInfoCommand() { + Restaurant testRestaurant = generateRestaurant(); + + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MainViewRestaurantInfoCommand.execute(testRestaurant); + String expectedOutput = "Restaurant name: Pho King\n" + "Restaurant address: NUS\n"; + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainReceiptOrderCommand_invalidOrder() { + ArrayList testOrdersList = new ArrayList<>(); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + MainReceiptOrderCommand.execute(testOrdersList, "receipt -order 2024123123"); + String expectedOutput = "Order not found\n"; + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } + + @Test + public void testMainReciptOrderCommand_validOrder() { + ArrayList testOrdersList = new ArrayList<>(); + testOrdersList.add(generateOrder()); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + MainReceiptOrderCommand.execute(testOrdersList, "receipt -order 12345"); + String expectedOutput = "+-----------------------------------------------------+\n" + + "| RECEIPT |\n" + + "+-----------------------------------------------------+\n" + + "| Pho King |\n" + + "| NUS |\n" + + "| |\n" + + "| Order Type: Takeaway |\n" + + "| Order ID: 12345 |\n" + + "+-----------------------------------------------------+\n" + + "| Item ID | Name | Unit Price | Quantity |\n" + + "+-----------------------------------------------------+\n" + + "| 1 | Burger | $6.00 | 1 |\n" + + "| 2 | Fries | $3.00 | 1 |\n" + + "| 3 | Sandwich | $4.00 | 1 |\n" + + "+-----------------------------------------------------+\n" + + "| Subtotal: $13.00 |\n" + + "+-----------------------------------------------------+\n" + + "| Service Charge (10.0%): $1.30 |\n" + + "| GST (9.0%): $1.29 |\n" + + "| Grand Total: $15.59 |\n" + + "+-----------------------------------------------------+\n" + + "| Cashier: John |\n" + + "+-----------------------------------------------------+\n" + + "\n"; + assertEquals(expectedOutput, outputContent.toString().replace("\r", "")); + } +} diff --git a/src/test/java/command/MenuCommandTest.java b/src/test/java/command/MenuCommandTest.java new file mode 100644 index 0000000000..696d0197a2 --- /dev/null +++ b/src/test/java/command/MenuCommandTest.java @@ -0,0 +1,156 @@ +package command; + +import command.menu.MenuAddCommand; +import command.menu.MenuCompleteCommand; +import command.menu.MenuDeleteCommand; +import command.menu.MenuExitCommand; +import command.menu.MenuHelpCommand; +import model.Menu; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MenuCommandTest { + @Test + public void testMenuHelp() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuHelpCommand.execute(); + String expectedOutput = "Here are the list of available commands:\n" + + "\thelp: Shows all the commands that can be used.\n" + + "\tadd -item -price : " + + "Adds a new item to the menu.\n" + + "\tdelete -item : Deletes an item from the menu.\n" + + "\tview items: Shows all the items in the current menu.\n" + + "\tcomplete: Completes the menu and returns to the main menu.\n" + + "\tcancel: Aborts the current menu and returns to the main menu.\n"; + + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testMenuAddNewItem() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + String expectedOutput = "Item successfully added to menu!\n" + + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Fried Chicken | $5.00 |\n" + + "+------+-----------------------------------+\n\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testMenuAddExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + String expectedOutput = "Item already in menu. It has ID: " + + "1" + " and Name: " + "Fried Chicken\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testMenuDeleteNonexistentItem() { + Menu newMenu = new Menu("1"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuDeleteCommand.execute(newMenu,"delete -item 7"); + String expectedOutput = "Item not found in menu\n" + + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "+------+-----------------------------------+\n\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testMenuDeleteOneExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuDeleteCommand.execute(newMenu,"delete -item 1"); + String expectedOutput = "Ice Cream is removed from menu\n" + + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "+------+-----------------------------------+\n\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testMenuDeleteMiddleExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuDeleteCommand.execute(newMenu,"delete -item 2"); + String expectedOutput = "Sundae is removed from menu\n" + + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Ice Cream | $2.00 |\n" + + "| 2 | Frappe | $4.00 |\n" + + "+------+-----------------------------------+\n\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + @Test + public void testMenuCompleteEmptyMenu() { + Menu newMenu = new Menu("1"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuCompleteCommand.execute(newMenu); + String expectedOutput = "Menu 1 is empty. Please add items to the menu.\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + @Test + public void testMenuCompleteNonEmptyMenu() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuCompleteCommand.execute(newMenu); + String expectedOutput = "Menu 1 has been saved!\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void menuExitEmptyMenu() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuExitCommand.execute(newMenu); + String expectedOutput = "Menu 1 cancelled\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void menuExitNonEmptyMenu() { + Menu newMenu = new Menu("1"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + MenuExitCommand.execute(newMenu); + String expectedOutput = "Menu 1 cancelled\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } +} diff --git a/src/test/java/command/OrderCommandTest.java b/src/test/java/command/OrderCommandTest.java new file mode 100644 index 0000000000..b0143762ab --- /dev/null +++ b/src/test/java/command/OrderCommandTest.java @@ -0,0 +1,380 @@ +package command; + +import command.menu.MenuAddCommand; +import command.order.OrderAddCommand; +import command.order.OrderViewItemsCommand; +import command.order.OrderExitCommand; +import command.order.OrderDeleteCommand; +import command.order.OrderCompleteCommand; +import command.order.OrderHelpCommand; +import command.order.OrderViewMenuCommand; +import model.Menu; +import model.Order; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OrderCommandTest { + @Test + public void testOrderHelp() { + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderHelpCommand.execute(); + String expectedOutput = "Here are the list of available commands:\n" + + "\thelp: Shows all the commands that can be used.\n" + + "\tadd -item -quantity : " + + "Adds the specified quantity \n\t\tof a particular menu item into the order.\n" + + "\tdelete -item -quantity : " + + "Deletes the specified quantity \n\t\tof a particular menu item from the order.\n"+ + "\tview menu: Shows the menu of the current order.\n" + + "\tview items: Shows the items in the current order.\n" + + "\tcomplete (-discount ): Completes the order and returns to the main menu.\n"+ + "\tcancel: Aborts the current order and returns to the main menu.\n"; + + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderAddOneExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderAddCommand.execute(newOrder, "add -item 1 -quantity 1",newMenu); + String expectedOutput = "1 Ice Cream is added to order\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderAddMaxExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 9999",newMenu); + String expectedOutput = "9999 Sundae is added to order\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderAddTenThousandExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 10000",newMenu); + String expectedOutput = "Quantity must be between 0 and 10000 (both exclusive)\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderAddZeroExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderAddCommand.execute(newOrder, "add -item 4 -quantity 0",newMenu); + String expectedOutput = "Quantity must be between 0 and 10000 (both exclusive)\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderAddNonExistingItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderAddCommand.execute(newOrder, "add -item 7 -quantity 2",newMenu); + String expectedOutput = "Item not found in menu\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderEmptyComplete() { + String response = "y"; + Scanner newScanner = new Scanner(response); + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderCompleteCommand.execute(newOrder, newScanner,"complete order"); + String expectedOutput = "Order " + newOrder.getId() + " is empty. Please add items to the order.\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderValidComplete() { + String response = "y"; + Scanner newScanner = new Scanner(response); + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderCompleteCommand.execute(newOrder, newScanner,"complete"); + String expectedOutput = "+-----------------------------------------------------+\n" + + "| RECEIPT |\n" + + "+-----------------------------------------------------+\n" + + "| abc restaurant |\n" + + "| abc street |\n" + + "| |\n" + + "| Order Type: Takeaway |\n" + + "| Order ID: " + newOrder.getId() + " |\n" + + "+-----------------------------------------------------+\n" + + "| Item ID | Name | Unit Price | Quantity |\n" + + "+-----------------------------------------------------+\n" + + "| 2 | Sundae | $3.00 | 2 |\n" + + "+-----------------------------------------------------+\n" + + "| Subtotal: $6.00 |\n" + + "+-----------------------------------------------------+\n" + + "| Service Charge (10.0%): $0.60 |\n" + + "| GST (9.0%): $0.59 |\n" + + "| Grand Total: $7.19 |\n" + + "+-----------------------------------------------------+\n" + + "| Cashier: mr abc |\n" + + "+-----------------------------------------------------+\n\n" + + + "WARNING: Once an order is completed, you are NOT ALLOWED to edit or delete it.\n" + + "Do you want to complete the order? (type 'y' to complete, anything else to cancel)\n"+ + + "Order " + newOrder.getId() + " is completed!\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderValidRejectComplete() { + String response = "k"; + Scanner newScanner = new Scanner(response); + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderCompleteCommand.execute(newOrder, newScanner,"complete"); + String expectedOutput = "+-----------------------------------------------------+\n" + + "| RECEIPT |\n" + + "+-----------------------------------------------------+\n" + + "| abc restaurant |\n" + + "| abc street |\n" + + "| |\n" + + "| Order Type: Takeaway |\n" + + "| Order ID: " + newOrder.getId() + " |\n" + + "+-----------------------------------------------------+\n" + + "| Item ID | Name | Unit Price | Quantity |\n" + + "+-----------------------------------------------------+\n" + + "| 2 | Sundae | $3.00 | 2 |\n" + + "+-----------------------------------------------------+\n" + + "| Subtotal: $6.00 |\n" + + "+-----------------------------------------------------+\n" + + "| Service Charge (10.0%): $0.60 |\n" + + "| GST (9.0%): $0.59 |\n" + + "| Grand Total: $7.19 |\n" + + "+-----------------------------------------------------+\n" + + "| Cashier: mr abc |\n" + + "+-----------------------------------------------------+\n\n" + + "WARNING: Once an order is completed, you are NOT ALLOWED to edit or delete it.\n" + + "Do you want to complete the order? (type 'y' to complete, anything else to cancel)\n"+ + + "Order " + newOrder.getId() + " is not completed.\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderDeleteNonExistentItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderDeleteCommand.execute(newOrder, "delete -item 5 -quantity 2",newMenu); + String expectedOutput = "Item not found in order\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderDeleteExcessExistentItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderDeleteCommand.execute(newOrder, "delete -item 2 -quantity 7",newMenu); + String expectedOutput = "All Sundae is removed from order\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderDeletePartialExistentItem() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderDeleteCommand.execute(newOrder, "delete -item 2 -quantity 1",newMenu); + String expectedOutput = "1 Sundae is removed from order\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderExitNonEmptyOrder() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderExitCommand.execute(newOrder); + String expectedOutput = "Order " + newOrder.getId() + " cancelled\n" + "Order not created\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderExitEmptyOrder() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderExitCommand.execute(newOrder); + String expectedOutput = "Order " + newOrder.getId() + " cancelled\n" + "Order not created\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + @Test + public void testOrderViewEmptyOrder() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderViewItemsCommand.execute(newOrder); + String expectedOutput = "Order is empty.\n"; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderViewNonEmptyOrder() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + Order newOrder = new Order("abc restaurant", "abc street", + "mr abc", "Takeaway"); + OrderAddCommand.execute(newOrder, "add -item 2 -quantity 2",newMenu); + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + OrderViewItemsCommand.execute(newOrder); + String expectedOutput = "+-----------------------------------------------------+\n" + + "| Current Order |\n" + + "+-----------------------------------------------------+\n" + + "| 2 | Sundae | $3.00 | 2 |\n" + + "+-----------------------------------------------------+\n" + + "| Subtotal: $6.00 |\n" + + "+-----------------------------------------------------+\n" + + "| Service Charge (10.0%): $0.60 |\n" + + "| GST (9.0%): $0.59 |\n" + + "| Grand Total: $7.19 |\n" + + "+-----------------------------------------------------+\n" + + "| Cashier: mr abc |\n" + + "+-----------------------------------------------------+\n\n" ; + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + + @Test + public void testOrderViewMenu() { + Menu newMenu = new Menu("1"); + MenuAddCommand.execute(newMenu,"add -item Ice Cream -price 2.00"); + MenuAddCommand.execute(newMenu,"add -item Sundae -price 3.00"); + MenuAddCommand.execute(newMenu,"add -item Frappe -price 4.00"); + MenuAddCommand.execute(newMenu,"add -item Fried Chicken -price 5.00"); + + ByteArrayOutputStream outputContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputContent)); + + String expectedOutput = "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Ice Cream | $2.00 |\n" + + "| 2 | Sundae | $3.00 |\n" + + "| 3 | Frappe | $4.00 |\n" + + "| 4 | Fried Chicken | $5.00 |\n" + + "+------+-----------------------------------+\n\n"; + + OrderViewMenuCommand.execute(newMenu); + assertEquals(expectedOutput,outputContent.toString().replace("\r","")); + } + +} diff --git a/src/test/java/model/MenuTest.java b/src/test/java/model/MenuTest.java new file mode 100644 index 0000000000..a81c9a5ff9 --- /dev/null +++ b/src/test/java/model/MenuTest.java @@ -0,0 +1,89 @@ +package model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MenuTest { + @Test + public void testEmptyMenu() { + Menu menu = new Menu("1 "); + + assertEquals( + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "+------+-----------------------------------+\n" + , menu.toString()); + + } + @Test public void testMenuWithOneItem() { + Menu menu = new Menu("02"); + menu.add(new MenuItem("1","Nasi Lemak",3.00)); + assertEquals( + "+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Nasi Lemak | $3.00 |\n" + + "+------+-----------------------------------+\n", + menu.toString()); + + } + @Test + public void testMenuAddAndRemove() { + + MenuItem dish001 = new MenuItem("1", "Chicken Rice", 3.50); + MenuItem dish002 = new MenuItem("2", "Nasi Lemak", 3.00); + MenuItem dish004 = new MenuItem("3", "Mee Siam", 3.50); + Menu menu = new Menu("1"); + + menu.add(dish001); + assertEquals("+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Chicken Rice | $3.50 |\n" + + "+------+-----------------------------------+\n", menu.toString()); + menu.add(dish002); + assertEquals("+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Chicken Rice | $3.50 |\n" + + "| 2 | Nasi Lemak | $3.00 |\n" + + "+------+-----------------------------------+\n", menu.toString()); + menu.add(dish004); + assertEquals("+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Chicken Rice | $3.50 |\n" + + "| 2 | Nasi Lemak | $3.00 |\n" + + "| 3 | Mee Siam | $3.50 |\n" + + "+------+-----------------------------------+\n", menu.toString()); + menu.remove("2"); + assertEquals("+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Chicken Rice | $3.50 |\n" + + "| 2 | Mee Siam | $3.50 |\n" + + "+------+-----------------------------------+\n", menu.toString()); + menu.remove("2"); + assertEquals("+------------------------------------------+\n" + + "| MENU |\n" + + "+------+-----------------------------------+\n" + + "| ID | Name | Price |\n" + + "+------+-----------------------------------+\n" + + "| 1 | Chicken Rice | $3.50 |\n" + + "+------+-----------------------------------+\n", menu.toString()); + } +} diff --git a/src/test/java/model/OrderTest.java b/src/test/java/model/OrderTest.java new file mode 100644 index 0000000000..9a2587434e --- /dev/null +++ b/src/test/java/model/OrderTest.java @@ -0,0 +1,61 @@ +package model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OrderTest { + + @Test + public void testMenuItemOutput() { + MenuItem dish01 = new MenuItem("D01", "Chicken Rice", 3.50); + MenuItem dish02 = new MenuItem("D02", "Nasi Lemak", 3.00); + MenuItem dish03 = new MenuItem("D03", "Mee Goreng", 4.00); + MenuItem dish04 = new MenuItem("D04", "Mee Siam", 3.50); + MenuItem dish05 = new MenuItem("D04", "Mee Siam", 3.50); + assertEquals("D01 Chicken Rice $3.50", dish01.toString()); + assertEquals("D02 Nasi Lemak $3.00", dish02.toString()); + assertEquals("D03 Mee Goreng $4.00", dish03.toString()); + assertEquals("D04 Mee Siam $3.50", dish04.toString()); + assertEquals("D04 Mee Siam $3.50", dish05.toString()); + } + + @Test + public void testOrderAddAndRemove() { + MenuItem dish01 = new MenuItem("D01", "Chicken Rice", 3.50); + MenuItem dish02 = new MenuItem("D02", "Nasi Lemak", 3.00); + MenuItem dish04 = new MenuItem("D04", "Mee Siam", 3.50); + MenuItem dish05 = new MenuItem("D04", "Mee Siam", 3.50); + Order order = new Order("ABC restaurant", "123 street", + "tom","dine in"); + order.add(dish01); + assertEquals(order.getId() + "\n" + + "1. D01 Chicken Rice $3.50", order.toString()); + order.add(dish02); + assertEquals(order.getId() + "\n" + + "1. D01 Chicken Rice $3.50" + "\n" + + "2. D02 Nasi Lemak $3.00", order.toString()); + order.add(dish04); + order.add(dish05); + assertEquals(order.getId() + "\n" + + "1. D01 Chicken Rice $3.50" + "\n" + + "2. D02 Nasi Lemak $3.00" + "\n" + + "3. D04 Mee Siam $3.50" + "\n" + + "4. D04 Mee Siam $3.50", order.toString()); + } + + @Test + public void testOrderSummary() { + MenuItem dish01 = new MenuItem("D01", "Chicken Rice", 3.50); + MenuItem dish02 = new MenuItem("D02", "Nasi Lemak", 3.00); + MenuItem dish04 = new MenuItem("D04", "Mee Siam", 3.50); + MenuItem dish05 = new MenuItem("D04", "Mee Siam", 3.50); + Order order = new Order("ABC restaurant", "123 street", + "tom","dine in"); + order.add(dish01); + order.add(dish02); + order.add(dish04); + order.add(dish05); + assertEquals(order.getId() + " {Total Price: " + order.getTotalPrice() + "}", order.getOrderSummary()); + } +} diff --git a/src/test/java/stats/OrderStatisticsTest.java b/src/test/java/stats/OrderStatisticsTest.java new file mode 100644 index 0000000000..c72d302c6c --- /dev/null +++ b/src/test/java/stats/OrderStatisticsTest.java @@ -0,0 +1,150 @@ +package stats; + +import model.Item; +import model.MenuItem; +import model.Order; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class OrderStatisticsTest { + + public static final double SERVICE_CHARGE = 1.10; + public static final double GST = 1.09; + + // make some dummy orders + private ArrayList createOrderForTest() { + ArrayList orders = new ArrayList<>(); + Order order1 = new Order("restaurant1", "address1", "user1", "orderType1"); + + order1.add(new MenuItem("1", "item1", 1.0)); + order1.add(new MenuItem("1", "item1", 1.0)); + + order1.add(new MenuItem("2", "item2", 2.0)); + order1.add(new MenuItem("2", "item2", 2.0)); + order1.add(new MenuItem("2", "item2", 2.0)); + order1.add(new MenuItem("2", "item2", 2.0)); + + order1.add(new MenuItem("3", "item3", 3.0)); + order1.add(new MenuItem("4", "item4", 4.0)); + order1.add(new MenuItem("5", "item5", 5.0)); + + Order order2 = new Order("restaurant2", "address2", "user2", "orderType2"); + order2.add(new MenuItem("1", "item1", 1.0)); + order2.add(new MenuItem("1", "item1", 1.0)); + order2.add(new MenuItem("1", "item1", 1.0)); + order2.add(new MenuItem("1", "item1", 1.0)); + order2.add(new MenuItem("1", "item1", 1.0)); + + order2.add(new MenuItem("2", "item2", 2.0)); + order1.add(new MenuItem("2", "item2", 2.0)); + order1.add(new MenuItem("2", "item2", 2.0)); + + order2.add(new MenuItem("3", "item3", 3.0)); + + orders.add(order1); + orders.add(order2); + + return orders; + } + + + @Test + void testGetBestSellingItemsWithOrders() { + OrderStatistics orderStatistics = new OrderStatistics(createOrderForTest()); + List bestSellingItems = orderStatistics.getBestSellingItems(); + // expected best selling items are item1 and item2 + assertEquals(2, bestSellingItems.size()); + assertEquals("item1", bestSellingItems.get(0).getName()); + assertEquals("item2", bestSellingItems.get(1).getName()); + } + + // test best selling items with empty orders + @Test + void testGetBestSellingItemsWithEmptyOrders() { + ArrayList orders = new ArrayList<>(); + OrderStatistics orderStatistics = new OrderStatistics(orders); + List bestSellingItems = orderStatistics.getBestSellingItems(); + // expected best selling items are empty + assertEquals(0, bestSellingItems.size()); + } + + // test for getGrossSales + @Test + void testGetGrossSales() { + ArrayList orders = createOrderForTest(); + OrderStatistics orderStatistics = new OrderStatistics(createOrderForTest()); + assertEquals(String.format("%.2f", 36 * SERVICE_CHARGE * GST), + String.format("%.2f", orderStatistics.getGrossRevenue())); + Order newOrder = new Order("restaurant3", "address3", "user3", "orderType3"); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("5", "item5", 999.0)); + orders.add(newOrder); + orderStatistics = new OrderStatistics(orders); + assertEquals(String.format("%.2f", 1038 * SERVICE_CHARGE * GST), + String.format("%.2f", orderStatistics.getGrossRevenue())); + } + + // test for getGrossSales with empty orders + @Test + void testGetGrossSalesWithEmptyOrders() { + ArrayList orders = new ArrayList<>(); + OrderStatistics orderStatistics = new OrderStatistics(orders); + assertEquals(0.0, orderStatistics.getGrossRevenue()); + } + + // test for getNetSales + @Test + void testGetNetSales() { + ArrayList orders = createOrderForTest(); + OrderStatistics orderStatistics = new OrderStatistics(createOrderForTest()); + assertEquals(String.format("%.2f", 36 * SERVICE_CHARGE), + String.format("%.2f", orderStatistics.getNetRevenue())); + Order newOrder = new Order("restaurant3", "address3", "user3", "orderType3"); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("5", "item5", 999.0)); + orders.add(newOrder); + orderStatistics = new OrderStatistics(orders); + assertEquals(String.format("%.2f", 1038 * SERVICE_CHARGE), + String.format("%.2f", orderStatistics.getNetRevenue())); + } + + // test for getNetSales with empty orders + @Test + void testGetNetSalesWithEmptyOrders() { + ArrayList orders = new ArrayList<>(); + OrderStatistics orderStatistics = new OrderStatistics(orders); + assertEquals(0.0, orderStatistics.getNetRevenue()); + } + + // test for totalOrders + @Test + void testTotalOrders() { + ArrayList orders = createOrderForTest(); + OrderStatistics orderStatistics = new OrderStatistics(createOrderForTest()); + assertEquals(2, orderStatistics.getOrderCount()); + Order newOrder = new Order("restaurant3", "address3", "user3", "orderType3"); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("1", "item1", 1.0)); + newOrder.add(new MenuItem("5", "item5", 999.0)); + orders.add(newOrder); + orderStatistics = new OrderStatistics(orders); + assertEquals(3, orderStatistics.getOrderCount()); + } + + // test for totalOrders with empty orders + @Test + void testTotalOrdersWithEmptyOrders() { + ArrayList orders = new ArrayList<>(); + OrderStatistics orderStatistics = new OrderStatistics(orders); + assertEquals(0, orderStatistics.getOrderCount()); + } +} diff --git a/src/test/java/ui/JunitParserTest.java b/src/test/java/ui/JunitParserTest.java new file mode 100644 index 0000000000..c96e24a1cb --- /dev/null +++ b/src/test/java/ui/JunitParserTest.java @@ -0,0 +1,54 @@ +package ui; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JunitParserTest { + //@@author adamzzq + @Test + void testAnalyzeInput_validInput() { + Parser parser = new Parser(); + assertEquals(CommandType.CREATE_ORDER, parser.analyzeInput("create order -menu 1")); + assertEquals(CommandType.VIEW_ORDER, parser.analyzeInput("view -order 2")); + assertEquals(CommandType.EXIT_MAIN, parser.analyzeInput("bye")); + assertEquals(CommandType.HELP, parser.analyzeInput("help")); + + // case-insensitive + assertEquals(CommandType.CREATE_ORDER, parser.analyzeInput("CrEaTe OrDeR -mEnU 99")); + assertEquals(CommandType.VIEW_ORDER, parser.analyzeInput("ViEw -OrDeR 2")); + } + + //@@author adamzzq + @Test + void testAnalyzeInput_invalidInput() { + Parser parser = new Parser(); + + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("asfdhih 123")); + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("create order -menu 1 2 3")); + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("view -order 1 2 3")); + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("edit -order 1 2 3")); + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("create order -menu")); + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("view -order")); + assertThrows(IllegalArgumentException.class, () -> parser.analyzeInput("edit -order")); + } + + //@@author adamzzq + @Test + void splitInputTest() { + Parser parser = new Parser(); + assertArrayEquals(new String[]{"7"}, parser.splitInput(CommandType.CREATE_ORDER, "create order -menu 7")); + assertArrayEquals(new String[]{"6"}, parser.splitInput(CommandType.VIEW_ORDER, "view -order 6")); + } + + //@@author adamzzq + @Test + void testSplitInput_invalidInput() { + Parser parser = new Parser(); + assertThrows(AssertionError.class, + () -> parser.splitInput(CommandType.CREATE_ORDER, "create -Order -menu")); + assertThrows(AssertionError.class, () -> parser.splitInput(CommandType.VIEW_ORDER, "view -order")); + } +}