diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fd8c44d086..e2062c4201 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,18 +33,18 @@ jobs: - name: Build and check with Gradle run: ./gradlew check - - name: Perform IO redirection test (*NIX) - if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (MacOS) - if: always() && runner.os == 'macOS' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (Windows) - if: always() && runner.os == 'Windows' - working-directory: ${{ github.workspace }}/text-ui-test - shell: cmd - run: runtest.bat \ No newline at end of file +# - name: Perform IO redirection test (*NIX) +# if: runner.os == 'Linux' +# working-directory: ${{ github.workspace }}/text-ui-test +# run: ./runtest.sh +# +# - name: Perform IO redirection test (MacOS) +# if: always() && runner.os == 'macOS' +# working-directory: ${{ github.workspace }}/text-ui-test +# run: ./runtest.sh +# +# - name: Perform IO redirection test (Windows) +# if: always() && runner.os == 'Windows' +# working-directory: ${{ github.workspace }}/text-ui-test +# shell: cmd +# run: runtest.bat \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2873e189e1..c496b560d3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,8 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +META-INF/MANIFEST.MF +data/ + +cafeCtrl.log +cafeCtrl.log.lck diff --git a/build.gradle b/build.gradle index ea82051fab..3a07d8d15f 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("seedu.cafectrl.CafeCtrl") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("cafectrl") archiveClassifier.set("") } @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = false } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..82c0ab1f3c 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:--------:|:----------------------------------------:|:---------: +![](images/aboutUs/shanice.jpg) | Shanice | [Github](https://github.com/ShaniceTang) | [Portfolio](team/shanicetang.md) +![](images/aboutUs/naychimin.png) | Naychi | [Github](https://github.com/NaychiMin/tp) | [Portfolio](team/naychimin.md) + | Zi Yi | [Github](https://github.com/ziyi105) | [Portfolio](team/ziyi105.md) + | Dexter Hoon | [Github](https://github.com/DextheChik3n) | [Portfolio](team/dexthechik3n.md) +![](images/aboutUs/zhongheng.jpg) | Chua Zhong Heng | [Github](https://github.com/Cazh1/tp) | [Portfolio](team/cazh1.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..1d26b3fc7c 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,486 @@ # Developer Guide +* Table of Contents + +* [Developer Guide](#developer-guide) + * [**Acknowledgements**](#acknowledgements) + * [**Setting up, getting started**](#setting-up-getting-started) + * [**General notes**](#general-notes) + * [**Design**](#design) + * [Architecture](#architecture) + * [How the architecture components interact with each other](#how-the-architecture-components-interact-with-each-other) + * [Ui component](#ui-component) + * [Parser component](#parser-component) + * [Storage component](#storage-component) + * [Data component](#data-component) + * [**Feature**](#feature) + * [Add Dish](#add-dish) + * [List Menu](#list-menu) + * [Add Order](#add-order) + * [Next Day](#next-day) + * [Previous Day](#previous-day) + * [List Ingredients](#list-ingredients) + * [List Sale By Day](#list-sale-by-day) + * [Pantry - isDishCooked()](#pantry---isdishcooked) + * [Pantry - calculateMaxDish()](#pantry---calculatemaxdish) + * [Delete Dish](#delete-dish) + * [Edit Price](#edit-price) + * [View Total Stock](#view-total-stock) + * [Buy Ingredient](#buy-ingredient) + * [Help](#help) + * [**Future Enhancements**](#future-enhancements) + * [Create an interface for `Pantry`](#create-an-interface-for-pantry) + * [Make `Ui` class singleton](#make-ui-class-singleton) + * [**Product scope**](#product-scope) + * [Target user profile](#target-user-profile) + * [Value proposition](#value-proposition) + * [**Requirements**](#requirements) + * [Non-functional requirements](#non-functional-requirements) + * [User stories](#user-stories) + * [**Glossary**](#glossary) + * [**Instruction for manual testing**](#instruction-for-manual-testing) + * [Launch and shutdown](#launch-and-shutdown) + * [Editing the price of a dish](#editing-the-price-of-a-dish) + -## Acknowledgements +## **Acknowledgements** -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +[addressbook-level2](https://github.com/se-edu/addressbook-level2)
+[addressbook-level3](https://github.com/se-edu/addressbook-level3) -## Design & implementation +-------------------------------------------------------------------------------------------------------------------- +## **Setting up, getting started** -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +Refer to the guide [_UserGuide_](UserGuide.md). +-------------------------------------------------------------------------------------------------------------------- +## **General notes** -## Product scope +Only relevant attributes/associations/methods will be included in the UML diagram. Some of them are omitted to avoid confusion. + +-------------------------------------------------------------------------------------------------------------------- +## **Design** + +### Architecture +![Architecture Diagram](images/ArchitectureDiagram.png) +
*Figure 1: Architecture Diagram* + +The ***Architecture Diagram*** given above explains the high-level design of the App. +Listed below is a brief summary outlining the primary components and their interrelationships. + +In summary, the user interacts with the Ui components, initiating a sequence that involves: +- `Parser` component for command interpretation +- `Command` component for execution +- `Data` component for managing application data + +The `Storage` component mainly handles interaction with external text files and main coordinates the interactions between the various Components. + +The bulk of the app’s work is done by the following components: +- `Ui` : The UI of the App. +- `Storage` : Reads data from, and writes data to, the text files. +- `Data` : Consists of all the classes that are involved in execution of commands. +- `Parser` : Makes sense of user input to return the appropriate command +- `Command` : Executes the command requested by the user. + +### 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 `bye`. + +![Architecture Encode Data](images/sequence/Architecture_Encode_Data.png) +
*Figure 2: Architecture Encode Sequence Diagram* + +1. User enters the command `bye` to the `Ui` +2. `Ui` passes the command as a string through the method `receiveUserInput('bye')` in `CafeCtrl` +3. `CafeCtrl` passes the string to `Parser` through the method `parseCommand('bye')` +4. `Parser` returns a new `exitCommand` object +5. `CafeCtrl` calls the `execute()` method of `Command` and returns after execution is completed (Step 6) +6. `CafeCtrl` calls the `saveAll()` command in `Storage` before terminating the application +7. `saveMenu()`, `saveOrderList()`, and `savePantryStock` are executed within the `saveAll()` method (Steps 8 - 13) +8. Application terminates. + +### Ui component +API: [Ui.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/ui/Ui.java) + +![Ui Class Diagram](images/class/Ui.png) +
*Figure 3: Ui Class Diagram* + +The `Ui` component is responsible for interacting with the user. Within CafeCtrl, `Ui` is instantiated by `Parser`, `Command`, `Main`, `Data`, and `Storage` components to access the print methods in `Ui.java`. + +In the Ui component, +- `Ui.java` consists of multiple methods that received the user input and prints messages to the system console for users to see +- `Messages.java` consists of multiple strings that contains greeting, command, and goodbye messages to be shown to user +- `ErrorMessages.java` consists of multiple strings that contain error messages to be shown to user when an incorrect command or exception has been returned + +### Parser component +API: [Parser.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/parser/Parser.java) + +![Parser Class Diagram](images/class/Parser.svg) +
*Figure 4: Parser Class Diagram* + +The `Parser` component is responsible for interpreting the user's input and return appropriate `Command` for execution. If the input is unrecognisable, `Parser` will return an `IncorrectCommand` which will display error message to the user through `Ui`. + +
**Note:** `CafeCtrl` only have access to the interface `ParserUtil` although the run-time type object is `Parser`. With this, we are able to decrease coupling between `CafeCtrl` and `Parser`, allowing for easier maintenance. This also ensures the testability as we could provide mock or stub dependencies during testing, we could isolate the behavior of the class and focus on unit testing without external dependencies.
+ +Below is the sequence diagram of a parser which shows how `Parser` parses user input: + +![Parser Parsing User Input Sequence Diagram](images/sequence/Parser.svg) +
*Figure 5: Parser Parsing User Input Sequence Diagram* + +When user input string is received through `Ui`, which passes the full user input to `Main`. `Main` then passes it to `Parser` via `parseCommand` for interpretation. In `parseCommand`, it finds the matching keyword for different command from the user input, it calls the respective `prepareCommand` method within itself. `prepareCommand` then generates the corresponding command class and return it to `parseCommand`, which returns the `Command` back to `Main` for execution. + +### Storage component +API: [Storage.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/storage/Storage.java) + +![Storage Class Diagram](images/class/Storage.png) +
*Figure 6: Storage Class Diagram* + +The `Storage` class, +- loads and saves the list of dishes on the `Menu`, available ingredient stock in `Pantry` and orders for the day in `OrderList` in a text file. +- detects if the file is tampered by the user by reading the hash number in the storage text files +- depends on `Menu`, `Pantry` and `Sales` objects (which are found in the data package). +- is composed of `FileManager` object as the text file needs to be located first before reading or writing. + +### Data component +Folder: [Data](https://github.com/AY2324S1-CS2113-T17-2/tp/tree/master/src/main/java/seedu/cafectrl/data) +![Data Class Diagram](images/class/Data.png) +
*Figure 7: Data Package Class Diagram* + +The 'Data' package consists of all the classes that the commands interact with to perform various functions. +A summary of the class diagram is as listed below: +- Each `Dish` within the `Menu` is constructed with a set of `Ingredient` instances, forming a one-to-many relationship with `Ingredient`. +- `Pantry` is instantiated with an ArrayList of `Ingredients` (`pantryStock`), forming a one-to-many relationship with `Ingredient`. +- The `Chef` class has a one-to-one relationship with `Pantry`, ensuring access to necessary ingredients for dish preparation. +- When an order is placed, the `Order` class is instantiated with an ArrayList of `Ingredient` (`ingredientList`), forming a one-to-many relationship with `Ingredient`. +- `OrderList` is instantiated with an ArrayList of `Order`, forming a one-to-many relationship with `Order`. +- `Sales` is instantiated with an ArrayList of `OrderList`, forming a one-to-many relationship with `OrderList`. +- Lastly, the `CurrentDate` class keeps track of the current operating day of the cafe. + +-------------------------------------------------------------------------------------------------------------------- +## **Feature** + +### Add Dish + +![Add Dish Execution](images/sequence/AddDishCommand_execute.png) +

*Figure 8: Execution of `add` Command Sequence Diagram* + +API: [AddDishCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/AddDishCommand.java) + +The `add` command, add a dish to the `Menu` object and prints out a formatted message to state the name, price and ingredients entered for the dish. + +when the `execute()` method from `AddDishCommand` is called in the main class `CafeCtrl`, the `addDish()` method is first called to add the `Dish` object to the `Menu`. It will then call the `printAddDishMessage()` method, which gets all the parameters of the `Dish` object (dishName, dishPrice, dishIngredients) and passes them to the `Ui` to then be printed out to the User. + +Separation of Concerns was applied to ensure the `Ui` is only responsible with only displaying messages while the `Menu` deals with the logic of adding dish to the menu. This implementation also encapsulates the details of adding a dish and displaying messages. For example, The `AddDishCommand` class doesn't need to know how the internal details of the dish adding and message printing are performed. + +### List Menu +A `list_menu` command can be used to display all the `Dish` objects stored in `Menu`. + +The following class diagram illustrates the relationship between the respective classes involved in the creation and execution of a list_menu command. +![List Menu Execution](images/class/ListMenuCommandClass.png) +
*Figure 9: Execution of `list_menu` Command Class Diagram* + +![List Menu Execution](images/sequence/ListMenuCommand_execute.png) +
*Figure 10: Execution of `list_menu` Command Sequence Diagram* + +API: [ListMenuCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/ListMenuCommand.java) + +When the `execute()` method of ListMenuCommand is invoked in Main, it checks if the size of the menu by running `menu.getSize()`. + +1) If the menu is empty, it will call its `printEmptyMenu()` method to display to the user a `MENU_EMPTY_MESSAGE` in the Ui object and returns afterward. +2) If the menu is not empty, it will call its `printFullMenu()` method. +`printFullMenu()` will first print the top portion of the menu using the Ui object. +It then iterates through the `Dish` objects in `Menu` in a "for" loop, using `menu.getDishFromId()` to retrieve the Dish object. +The `dishName` and `dishPrice` are both access from `Dish` Class using `getName()` and `getPrice()` respectively. +The data are then packaged nicely in a `leftAlignFormat`, with (indexNum + ". " + dishName," $" + dishPrice) such that + e.g. (1. Chicken Rice $2.50) is shown. + +### Add Order +An add_order command can be used to add `order` to an `orderList` in `Sales`. + +The following class diagram illustrates the relationship between the respective classes involved in the creation and execution of an add_order command. +![Add_Order Execution](images/class/AddOrderCommandClass.png) +
*Figure 11: Execution of `add_order` Command Class Diagram* + +![Add_order Execution](images/sequence/AddOrderCommand_execute.png) +
*Figure 12: Execution of `add_order` Command Sequence Diagram* + +API: [AddOrderCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/AddOrderCommand.java) + +When the `execute()` method of AddOrderCommand is invoked in Main, the parsed `order` object is added to the `orderList`. + +A `Chef` object is then created to process the order by running `cookDish()`. +This method first checks if the order has already been completed by running `order.getIsCompleted()`. +If the order has not been completed, the `showChefMesage()` in the Ui component is triggered to display a message to show the user that the dish is being 'prepared'. +An ArrayList of Ingredients, ingredientList, is retrieved from the `order` object by `order.getIngredientList()`. +This ingredientList is passed into the `pantry` object in `pantry.isDishCook()` to process the ingredients used from the pantry stock. +This method returns a boolean, true if there is sufficient ingredients in the pantry, false is insufficient. +The order completeness status is updated by the boolean method, passing it into `order.setComplete()` + +Returning to the AddOrderCommand, the `order` object is checked to be completed again by running `order.getIsCompleted()`. +This verifies that the has been successfully completed. +After verifying that the order has been completed, the cost of the order is added to the total order by `orderList.addCost()`. +The total cost is the shown to the user using `ui.showOrderStatus`. +Lastly, the pantry checks on the remaining ingredients in stock and calculates the potential future dishes able to be made with the remaining stock, using `pantry.calculateDishAvailability()`. + +If the order has been marked incomplete, the details of the orderedDish is retrieved from `Order` using `order.getOrderedDish()`. +This is then passed on to the `Pantry` to figure out the missing ingredients, by `pantry.calculateMaxDishes()`. +Lastly, the user is shown a message informing that the order has not been completed due to the lack of ingredients using `ui.showIncompleteOrder()`. + +### Next Day +A `next_day` command can be used advance the current day. + +The following class diagram illustrates the relationship between the respective classes involved in the creation and execution of a next_day command. +![Next_Day Execution](images/class/NextDayCommandClass.png) +
*Figure 13: Execution of `next_day` Command Class Diagram* + +![Next_Day Execution](images/sequence/NextDayCommand_execute.png) +
*Figure 14: Execution of `next_day` Command Sequence Diagram* + +API: [NextDayCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/NextDayCommand.java) + +When the `execute()` method of NextDayCommand is invoked in Main, the day in the program is advanced by 1 day, by running `currentDate.nextDay()`. + +The next day data is retrieved from the `CurrentDate` object using `currentDate.getCurrentDay()`. +This next day data is compared with the days accounted for in the `Sales` object, retrieved using `sales.getDaysAccounted()`. + +If the next day is more than the number of days accounted in sales, this means that there is no `orderList` prepared for the coming day. +A new `OrderList` object is created using `new OrderList()`, and added into the `Sales` object by running `sales.addOrderList()`. +Following this, the day has been accounted and this is updated through `sales.nextDay()`. + +To end off the command, `ui.showNextDay()` is run to display a message to the user a prepared message for advancing the day. +The user is also shown the advanced day number. + +### Previous Day +A `previous_day` command can be used to recede the current day. + +The following class diagram illustrates the relationship between the respective classes involved in the creation and execution of a next_day command. +![Previous_Day Execution](images/class/PreviousDayCommandClass.png) +
*Figure 15: Execution of `previous_day` Command Class Diagram* + +![Previous_Day Execution](images/sequence/PreviousDayCommand_execute.png) +
*Figure 16: Execution of `previous_day` Command Sequence Diagram* + +API: [PreviousDayCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/PreviousDayCommand.java) + +When the `execute()` method of PreviousDayCommand is invoked in Main, the day in the program is receded by 1 day, by running `currentDate.previousDay()`. + +To end off the command, `ui.showPreviousDay()` is run to display a message to the user a prepared message for receding the day. +The user is also shown the receded day number. + +### List Ingredients +![List Ingredient Execution](images/sequence/ListIngredientCommand_execute.svg) +
*Figure 17: Execution of `list_ingredient` Command Sequence Diagram* + +API: [ListIngredientCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/ListIngredientCommand.java) + +The diagram above omits the showToUser() function in the Ui class to prevent unnecessary sophistication. +Although it may seem tedious the steps are essentially as listed below: +- The sequence begins with the `Main` class invoking the `execute` method of the `ListIngredientCommand` after using a parser command. +- The `ListIngredientCommand` communicates with the `Menu` class, invoking the `getMenuItemsList()` method to retrieve a list of menu items. The function returns an ArrayList of objects of 'Dish' type. +- The `ListIngredientCommand` communicates with the `Ui` class, invoking the `showListIngredients()` method to print out the list of ingredients used for the selected dish. +- The `Ui` class communicates with the `Dish` class, invoking the `getIngredients()` method to obtain the list of ingredients for the selected dish. The `Dish` class responds with an ArrayList of objects of 'Ingredient' type to the `Ui` class. +- There is a loop that iterates through each ingredient in the list. The `Ui` class interacts with the `Ingredients` class, to obtain the name, quantity and unit of the ingredient. +- The `Ui` class showcases the information to the user through the `formatListIngredient()` method. + +### List Sale By Day +![List_Sale Execution](images/sequence/ListSaleByDayCommand_execute.svg) +
*Figure 18: Execution of `list_sale` Command Sequence Diagram* + +API: [ListSaleByDayCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/ListSaleByDayCommand.java) + +The diagram above omits the showToUser() function in the Ui class to prevent unnecessary sophistication. +The steps are essentially as listed below: +- The sequence starts with the invocation of the `execute()` method in the `ListSaleByDayCommand` class, which the invokes the `sales.printSaleByDay()` method. +- The `Sales` class interacts with the `OrderList` class to check if there are completed orders to be displayed. +- If there are no completed orders or no orders at all, a message is shown to the user via the `Ui` class and the command's execution ends. +- If there are completed orders, the process continues to display it in a table format. + - `showSalesTop()`: Display table header + - `orderList.printOrderList()`: The OrderList iterates over each order, aggregates orders, and prints details for each aggregated order. For each aggregated order, details like dish name, quantity, and total order cost are retrieved from the Order class and shown to the user via the Ui class. + - `showSalesCost()`: Displays the total sales cost for the aggregated orders. + - `showSalesBottom()`: Displays the bottom of the table + +* The List Total Sales command follows a comparable sequence, and as such, it will be excluded to avoid the repetition of multiple similar diagrams. + +### Pantry - isDishCooked() +![isDishCooked_function](images/sequence/Pantry_IsDishCooked.svg) +
*Figure 19: Data Processing of `isDishCooked()` Function Used in `add_order` Command Sequence Diagram* + +API: [Pantry.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/data/Pantry.java) + +This section briefly explains how `add_order` checks if the order added is successfully cooked. +The steps are essentially as listed below: +- The sequence starts with the invocation of `isDishCooked()` to the `Pantry` class, with a list of ingredients needed for the order. +- For each ingredient in the dish, `isDishCooked()` first obtains the quantity of the ingredients needed (`usedQty`) for the order as shown in step 4 and 5. +- The function then attempts to get the Ingredient used from the current stock in the Pantry (`stockQty`) as shown in steps 11 and 12. + - If `usedIngredientFromStock` is null, it means that the ingredient does not exist in the Pantry and the sequence ends with a `false` being returned. + - If `usedIngredientFromStock` exists but the quantity is insufficient, + - If `usedIngredientFromStock` is found and the quantity is sufficient, the used quantity is deducted from the stock quantity in the Pantry and the sequence ends with a `true` being returned. +- A `false` indicates that the order was unsuccessful while a `true` indicates that the order was successful. + +### Pantry - calculateMaxDish() +![calculateMaxDish_function](images/sequence/Pantry_CalculateMaxDish.svg) +
*Figure 20: Data Processing of `calculateMaxDish()` Function Used in `add_order` Command Sequence Diagram* + +API: [Pantry.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/data/Pantry.java) + +This section briefly explains how `add_order` checks if restocking of ingredients is needed. +The steps are essentially as listed below: +- The sequence starts with the invocation of `calculateMaxDish()` to the `Pantry` class. +- Steps 2 to 6 involves retrieving the ingredients used to make the dish. +- The function `calculateMaxDishForEachIngredient` returns an integer and assigns it to the variable `numOfDish` which is the maximum number of dishes that can be cooked. +- If the order is incomplete + - ingredients that need restocking will be passed into the `handleRestock` function. +- If the order is complete, + - ingredients that are unable to prep the next dish will be passed into the `handleRestock` function. + +### Delete Dish + +![Delete Dish Execution](images/sequence/DeleteDishCommand_execute.png) +
*Figure 21: Execution of `delete` Command Sequence Diagram* + +API: [DeleteDishCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/DeleteDishCommand.java) + +When the `execute()` method of `DeleteDishCommand` is invoked in `Main`, it subsequently calls `getMenuItemsList().get(dishIndexToBeDeleted)` method on the `Menu` object to retrieve the `Dish` object to be deleted. +Following this, the `showDeleteMesage()` method in the Ui component is triggered to display a message to show the user which dish is about to be deleted. +Afterward, `DeleteDishCommand` calls `removeDish(dishIndexToBeDeleted)` of the `Menu` object to remove the selected dish at the index indicated by the user. +This sequence of actions orchestrates the flow of information and operations between `Main`, `DeleteDishCommand`, `Menu`, and `Ui` components, ensuring the seamless handling of the dish deleting functionality within the application. + + +`DeleteDishCommand` is implemented in such a way because: +1. It promotes loose coupling between components. For instance, `Main` doesn't need to know the details of how the `execute()` of `DeleteDishCommand` is executed or how the message is displayed in `Ui`. +2. Each component has a specific role and responsibility. `Main` is responsible for receiving user input and invoking `execute()`, `DeleteDishCommand` is responsible for encapsulating the delete operation, `Menu` is responsible for managing the menu items, and `Ui` is responsible for displaying messages to the user. This separation of concerns makes the code more maintainable and easier to understand. + +### Edit Price + +![Edit Price Execution](images/sequence/EditPriceCommand_execute.png) +
*Figure 22: Execution of `edit_price` Command Sequence Diagram* + +API: [EditPriceCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/EditPriceCommand.java) + +When the `execute()` method of `EditPriceCommand` is invoked in `Main`, it subsequently calls the `setPrice()` method on the `Dish` object to modify the price of the specific dish. Following this, the `showEditPriceMessages()` method in the `Ui` component is triggered to retrieve and display a message from `Messages` related to the successful execution of the price modification process. This sequence of actions orchestrates the flow of information and operations between the `Main`, `EditPriceCommand`, `Dish`, and `Ui` components, ensuring the seamless handling of the price editing functionality within the application. + +### View Total Stock +![View Total Stock Execution](images/sequence/ViewTotalStockCommand_execute.png) + +*Figure 23: Execution of view_stock command* + +API: [ViewTotalStockCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/ViewTotalStockCommand.java) + +When the `execute()` method of `ViewTotalStock` is invoked, an ArrayList of Ingredients are retrieved through the method `getPantryStock`. For each ingredient in the ArrayList, `ViewTotalStock` calls `showIngredientStock` from `Ui` to print out the list of ingredients in the ArrayList. + +### Buy Ingredient +![Buy Ingredient Execution](images/sequence/BuyIngredientCommand_execute.png) + +*Figure 24: Execution of buy_ingredient command* + +API: [BuyIngredientCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/BuyIngredientCommand.java) + +When the `execute()` method is invoked +1. `addIngredient` in `BuyIngredientCommand` is called +2. Looping from the **first to the last** element in an ArrayList of Ingredients called `ingredients`, `addIngredientToStock` from `Pantry` is invoked +3. First, `pantryStock` is retrieved and `getIndexOfIngredient` is called to check if the new ingredient exists in `pantryStock` +4. If ingredient exists (`ingredientIndex != -1`), `addIngredientQuantity` is called to update the quantity of the existing ingredient +5. Else, a new `ingredient` object is returned +6. Looping from the **last to the first** element in `ingredients`, the ingredient is added to the string to be printed `ingredientString` using the `buildBuyIngredientMessage` method which ignores repeated ingredients in the list +7. Finally, `ingredientString` is shown to the user through `showToUser` method of `Ui` + +### Help + +![Help Execution](images/sequence/HelpCommand_execute.svg) +
*Figure 25: Execution of `help` Command Sequence Diagram* + +API: [HelpCommand.java](https://github.com/AY2324S1-CS2113-T17-2/tp/blob/master/src/main/java/seedu/cafectrl/command/HelpCommand.java) + +When the `execute()` method of `HelpCommand` is invoked in `Main`, it subsequently calls the `showHelp()` method in `Ui`. In `showHelp()`, messages related to command usage will be retrieved and be printed out using by self-invoking `showToUserWithSpaceInBetweenLines(messages: String...)`. + +-------------------------------------------------------------------------------------------------------------------- +## **Future Enhancements** +### Create an interface for `Pantry` + - **Problem**: `Pantry` class is used in testing of methods such as `addOrder`. With this implementation, we are unable to test the `addOrder` feature in isolation as any bugs in `Pantry` class could potentially affect the behaviour of `addOrder` feature. + - **Solution**: Instead of using the concrete `Pantry` class, `addOrder` could use an interface `PantryUtil` to access the required methods. A hard coded class that is less prone to bugs can be used to substitute the actual `Pantry` class by implementing a `PantryUtil` interface.
With this, we are able to test the method in isolation as we have removed the dependency on the actual `Pantry` class. +### Make `Ui` class singleton + - **Problem**: As we need to use the same `Ui` instance for all methods to avoid repeated instantiation of `Scanner` which could slow down the application, the same `Ui` instance is being passed to the constructor for all `Command` classes. This makes the parameters for the constructor looks too long. + - **Solution**: Implement a static `getInstance` method in `Ui` class which, when it is called for the first time, creates a new instance of `Ui` and store it in a static constant in the `Ui` object. The method will return the `ui` object in the constant for subsequent `getInstance` call.
With this implementation, we no longer need to pass `ui` around as we can access the same `ui` object by calling `getInstance`. + +
![Class diagram for singleton Ui](images/class/ui_singleton.png) +
*Figure 26: singleton Ui Class Diagram* + +
![Sequence diagram for singleton Ui](images/sequence/ui_singleton.png) +
*Figure 27: `getinstance` call on `Ui` Sequence diagram* + +-------------------------------------------------------------------------------------------------------------------- +## **Product scope** ### Target user profile -{Describe the target user profile} +Café proprietors who ***love*** typing on CLI and are seeking for a software solution to optimize the management of their café's operations. ### Value proposition -{Describe the value proposition: what problem does it solve?} +Our product aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers users to streamline stock inventory, menu and orders. Users will also briefly be able to gain valuable insights through comprehensive sales reporting, enabling them to analyze sales trends and calculate revenue/profit margins, eliminating the need for cross-platform management. + +-------------------------------------------------------------------------------------------------------------------- +## **Requirements** +### Non-functional requirements -## User Stories +1. This application requires the use of Java 11. +2. This application should work on most mainstream OS. +3. This application should be able to work offline -|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| +### User stories -## Non-Functional Requirements +| Priority | As a …​ | I want to …​ | So that I can…​ | +|-------------------------------|-----------------------------------------------------------------------|---------------------------------------------------------|-----------------------------------------------------------------------------------------| +| `* * *` | cafe owner who is responsible for coming up with new dish | add dish to the menu | add new dish to the menu | +| `* * *` | cafe manager is responsible for managing pantry stock | track the inventory levels for ingredients and supplies | know what ingredients I need to restock | +| `* * *` | cafe manager is responsible for managing pantry stock | buy ingredients | restock low stock ingredients | +| `* * *` | cafe owner who is also the chef | view the ingredients needed for a dish | know what ingredients to use when cooking a dish | +| `* * *` | cafe owner who wants to maximise profit | edit the price of the dish | increase the price of the dish when there is inflation | +| `* * *` | cafe owner who cares about the sales of the cafe | view the sales of the cafe | know whether my cafe is profiting | +| `* * *` | cafe owner who works 7 days a week | save the menu, pantry stock and order | have access to the same menu, pantry stock and orders when I go back to work | +| `* * *` | cafe owner who is responsible for placing order | add order | ask the chef to cook the order | +| `* *` | cafe manager who is responsible for drafting the menu | view the menu | keep track of what dish we have | +| `* *` | cafe owner who working 7 days a week | fast forward to the next day | close the cafe and call it a day when I am tired | +| `* *` | clumsy cafe owner who works 7 days a week | go back to the previous day | still accept order from the previous day if I accidentally fast forward to the next day | +| `* *`
(to be implemented) | cafe owner who is interested to know the popularity of the menu items | view the rank of popularity based on order history | adjust the pricing or remove the dish that is not popular | -{Give non-functional requirements} +-------------------------------------------------------------------------------------------------------------------- +## **Glossary** -## Glossary +- **Mainstream OS**: Windows, Linux, Unix, OS-X +- **Qty**: Quantity +- **CLI**: Command Line Interface -* *glossary item* - Definition +-------------------------------------------------------------------------------------------------------------------- +## **Instruction for manual testing** +**Note:** These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing. -## Instructions for manual testing +### Launch and shutdown +1. Initial launch + 1. Download the jar file and copy into an empty folder + 2. Double-click the jar file Expected: + ``` + Hello! Welcome to + _/_/_/ _/_/ _/_/_/ _/ _/ + _/ _/_/_/ _/ _/_/ _/ _/_/_/_/ _/ _/_/ _/ + _/ _/ _/ _/_/_/_/ _/_/_/_/ _/ _/ _/_/ _/ + _/ _/ _/ _/ _/ _/ _/ _/ _/ + _/_/_/ _/_/_/ _/ _/_/_/ _/_/_/ _/_/ _/ _/ + ------------------------------------------------------------------------ + > + ``` +2. To exit the application, input `bye` in the CLI -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +### Editing the price of a dish +1. Prerequisites: Have at least one dish in the menu. Dish can be added with the following command:
+ ```add name/chicken rice price/3.00 ingredient/rice qty/200g, ingredient/chicken qty/100g``` +2. Edit price of the dish by using the following command:
+ ```edit_price dish/1 price/4.50``` +3. Expected outcome:
+ ``` + Price modified for the following dish: + chicken rice $4.50 + ``` +3. Exit the application with command:
+ ```bye``` +4. In the menu.txt file under data folder, the following data can be found:
+ ``` + chicken rice | 4.5 | rice - 50 - g | chicken - 100 - g + ``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..4c07902d8a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# CafeCTRL -{Give product intro here} +CafeCTRL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers Café proprietors to streamline inventory and menu management. Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..82efd90ae1 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,470 @@ # User Guide +* Table of Contents + +* [User Guide](#user-guide) + * [Introduction](#introduction) + * [Quick Start](#quick-start) + * [Summary](#summary) + * [Features](#features) + * [Viewing help : `help`](#viewing-help--help) + * [Adding a dish : `add`](#adding-a-dish--add) + * [Deleting a dish : `delete`](#deleting-a-dish--delete) + * [Editing price of a dish : `edit_price`](#editing-price-of-a-dish--edit_price) + * [Listing all dishes : `list_menu`](#listing-all-dishes--list_menu) + * [Listing ingredients needed for the selected dish : `list_ingredients`](#listing-ingredients-needed-for-the-selected-dish--list_ingredients) + * [Buying an ingredient : `buy_ingredient`](#buying-an-ingredient--buy_ingredient) + * [Viewing the total stock of ingredients : `view_stock`](#viewing-the-total-stock-of-ingredients--view_stock) + * [Adding an order : `add_order`](#adding-an-order--add_order) + * [Showing total sales : `list_total_sales`](#showing-total-sales--list_total_sales) + * [Showing sales for a chosen day : `list_sale`](#showing-sales-for-a-chosen-day--list_sale) + * [Advancing to the next day: `next_day`](#advancing-to-the-next-day-next_day) + * [Returning to the previous day: `previous_day`](#returning-to-the-previous-day-previous_day) + * [Exiting the program : `bye`](#exiting-the-program--bye) + * [Known Issues](#known-issues) + * [Command Summary](#command-summary) + * [Glossary](#glossary) + +--------------------------------------------------- ## Introduction -{Give a product intro} +CaféCTRL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers users to streamline stock inventory, menu and orders. Users will also briefly be able to gain valuable insights through comprehensive sales reporting, enabling them to analyze sales trends and calculate revenue/profit margins, eliminating the need for cross-platform management. +--------------------------------------------------- ## Quick Start +1. Ensure that you have Java `11` installed. +2. Down the latest version of `CafeCtrl` from [here](https://github.com/AY2324S1-CS2113-T17-2/tp/releases). +3. Copy the file to the folder you want to use as the home folder for your Cafe Manager CLI Application. +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar tp.jar` command to run the application. +5. If the setup is correct, you should see something like the below as the output: +``` +Hello! Welcome to + _/_/_/ _/_/ _/_/_/ _/ _/ + _/ _/_/_/ _/ _/_/ _/ _/_/_/_/ _/ _/_/ _/ + _/ _/ _/ _/_/_/_/ _/_/_/_/ _/ _/ _/_/ _/ +_/ _/ _/ _/ _/ _/ _/ _/ _/ + _/_/_/ _/_/_/ _/ _/_/_/ _/_/_/ _/_/ _/ _/ +------------------------------------------------------------------------ +> +``` -{Give steps to get started quickly} +--------------------------------------------------- + +## Summary +In CaféCTRL, the user is able to craft and **add dish** to the menu. If needed, he/she can **delete** a dish or *edit* the price of the dish that is already in the menu. +When there is a new order, the user can **add the order** and prepare it. If there is insufficient stock of ingredients, the user can **buy ingredients**. At the end of the day, the user can check the **sales of the day** or the **total sales** since day one. The user can advance to the **next day** or go back to the **previous day** to take in orders of the day. -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +--------------------------------------------------- + +## Features +> **Notes about command format:** +> - Words in `UPPER_CASE` are the arguments to be supplied by user.
+ e.g. in add name/NAME, NAME is a parameter that can be used as add name/Chicken. +> - Parameters need to be in the exact format as specified.
+ e.g. `add name/DISH_NAME price/PRICE ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY`,
`name/` must come before `price/`. +> - Items in square brackets with …​ can be used multiple times including zero times.
+ e.g. `add name/DISH_NAME price/PRICE ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY [, ingredient/INGREDIENT2_NAME qty/INGREDIENT2_QTY, ...]`
+ can be used as
+ `add name/Christmas Ham price/50.00 ingredient/Ham qty/1000g`
+ or as
+ `add name/chicken rice price/2.00 ingredient/rice qty/100g, ingredient/chicken qty/200g, ingredient/garlic qty/100g` +> - Items in angle brackets shows the different arguments that must be used at least once.
+ e.g. `qty/INGREDIENT2_QTY`
+ can be used as
+ `qty/100g` or `qty/100ml` +> - Extraneous parameters for commands that do not take in parameters (such as `help`, `list_menu`, `bye`) will be ignored.
+ e.g. if the command specifies `help 123`, it will be interpreted as `help`. -## Features + +### Viewing help : `help` +Shows a message explaining how to use all the commands -{Give detailed description of each feature} +Format: `help` +Output: +``` +------------------------------------------------------------------------ +These are all the commands I recognise: -### Adding a todo: `todo` -Adds a new item to the list of todo items. +- Words in UPPER_CASE are the parameters to be supplied by the user. + e.g. in add name/NAME, NAME is a parameter that can be used as add name/Chicken. +- Parameters in [] are optional. -Format: `todo n/TODO_NAME d/DEADLINE` +------------------------------------------------------------------------ +To add a new dish to the menu: +add name/DISH_NAME price/DISH_PRICE ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY[, ingredient/INGREDIENT2_NAME, qty/INGREDIENT2_QTY...] +Example:add name/chicken rice price/3.00 ingredient/rice qty/200g, ingredient/chicken qty/100g +------------------------------------------------------------------------ +To delete a menu item: +deleteParameters: INDEX +Example: delete 1 +------------------------------------------------------------------------ +To edit price of a menu item: +edit_price dish/DISH_INDEX price/NEW_PRICE +Example: edit_price dish/1 price/4.50 +------------------------------------------------------------------------ +To view menu: +list_menu +------------------------------------------------------------------------ +To list out the ingredients needed along with the quantity for a specific dish: +Parameters: index/DISH_INDEX +Example: list_ingredients dish/1 +------------------------------------------------------------------------ +To buy ingredient: +buy_ingredient ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY[, ingredient/INGREDIENT2_NAME, qty/INGREDIENT2_QTY...] +Example:buy_ingredient ingredient/milk qty/200ml, ingredient/chicken qty/100g +------------------------------------------------------------------------ +To view pantry stock: +view_stock +------------------------------------------------------------------------ +To add a new order: +add_order name/DISH_NAME qty/QUANTITY +Example: add_ordername/chicken rice qty/2 +------------------------------------------------------------------------ +To show sales for all days: +list_total_sales +------------------------------------------------------------------------ +To show sales for a chosen day: +list_sale day/DAY_TO_DISPLAY +Example: list_sale day/1 +------------------------------------------------------------------------ +To travel to next day: +next_day +------------------------------------------------------------------------ +To go back to previous day: +previous_day +------------------------------------------------------------------------ +To exit: +bye +------------------------------------------------------------------------ +To view all commands: +help +------------------------------------------------------------------------ +------------------------------------------------------------------------ +``` + +### Adding a dish : `add` +Adds a dish consisting of its ingredients to the menu -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Format: `add name/DISH_NAME price/PRICE ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY[, ingredient/INGREDIENT2_NAME qty/INGREDIENT2_QTY, ...]` -Example of usage: +* `DISH_NAME` can contain up to 35 alphanumeric characters with whitespaces +* `PRICE` must be a positive number and can be up to 2 decimal places. +* `INGREDIENT_QTY` must be a positive integer and contain the unit **ml** or **g** specifically. + * e.g. `qty/50g` or `qty/1000ml` + * Ingredients that are solid should use the `g` unit while ingredients that are liquid should use the `ml` unit -`todo n/Write the rest of the User Guide d/next week` +Example: +``` +> add name/chicken rice price/2.00 ingredient/rice qty/100g, ingredient/chicken qty/200g, ingredient/soup qty/50ml +You have added the following dish... ++-------------------------------------------------------+ +| Dish: chicken rice | ++----------------------------------------+--------------+ +| Price: $2.00 | ++----------------------------------------+--------------+ +| Ingredient + Quantity | ++----------------------------------------+--------------+ +| rice | 100g | +| chicken | 200g | +| soup | 50ml | ++-------------------------------------------------------+ +``` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` + +### Deleting a dish : `delete` +Deletes a specific dish from the menu -## FAQ +Format: `delete DISH_INDEX` -**Q**: How do I transfer my data to another computer? +* Deletes the dish at the specified DISH_INDEX +* The index refers to the index number shown in the menu list +* The index must be a positive integer -**A**: {your answer here} +Example: `delete 1` +Output: +``` +Okay! chicken rice is deleted! :) +``` + +### Editing price of a dish : `edit_price` +Edits the price of an existing dish on the menu + +Format: `edit_price dish/DISH_INDEX price/NEW_PRICE` + +* `NEW_PRICE` must be a positive number and can have up to 2 decimal places. +* The index refers to the index number shown in the menu list +* The index must be a positive integer + +Example: `edit_price dish/1 price/4.50` + +Output: +``` +Price modified for the following dish: +chicken rice $4.50 +``` + + +### Listing all dishes : `list_menu` +Shows a list of all dishes on the menu + +Format: `list_menu` + +Example: +``` ++-------------------------------------------------------+ +| Ah, behold, the grand menu of delights! | ++----------------------------------------+--------------+ +| Dish Name | Price | ++----------------------------------------+--------------+ +| 1. chicken rice | $2.50 | +| 2. chicken curry | $4.30 | ++-------------------------------------------------------+ +``` + + +### Listing ingredients needed for the selected dish : `list_ingredients` +Lists out the ingredients needed along with the quantity for a specific dish + +Format: `list_ingredients dish/DISH_INDEX` + +Example: +- list followed by list_ingredients dish/1 lists the ingredients of the 1st dish on the menu + +``` ++-------------------------------------------------------+ +|Dish: chicken rice | ++----------------------------------------+--------------+ +| Ingredient + Quantity + ++----------------------------------------+--------------+ +| rice | 100g | +| chicken | 200g | +| soup | 50ml | ++-------------------------------------------------------+ +``` + +### Buying an ingredient : `buy_ingredient` +Adds one or more ingredients to the pantry + +Format: `buy_ingredient ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY[, ingredient/INGREDIENT2_NAME qty/INGREDIENT2_QTY, ...]` + +* `INGREDIENT_QTY` must be a positive integer and contain the unit ml or g specifically + * e.g. `qty/50g` or `qty/1000ml` + +Example: `buy_ingredient ingredient/chicken qty/500g, ingredient/milk qty/1000ml` + +Output: +``` +Added to stock: +Ingredient: milk +Total Qty: 1000ml + +Ingredient: chicken +Total Qty: 500g +``` + + +### Viewing the total stock of ingredients : `view_stock` +Displays the available stock of all the ingredients found in the pantry + +Format: `view_stock` + +Output: +``` ++-------------------------------------------------------+ +| You have the following ingredients in pantry: | ++----------------------------------------+--------------+ +| Ingredients | Qty | ++----------------------------------------+--------------+ +| chicken | 300g | +| noodles | 2100g | +| rice | 2900g | +| bread | 500g | ++-------------------------------------------------------+ +``` + + +### Adding an order : `add_order` +Adds an order consisting of dishes off the menu to an order list + +Format: `add_order name/DISH_NAME qty/DISH_QTY` + +* The `DISH_QTY` must be a positive integer number. + +Example: +``` +> add_order name/chicken rice qty/2 +I'm busy crafting your selected dish in the virtual kitchen of your dreams. Bon appétit! +----------------------------------------------------- +Order is ready! +Total order cost: $5.00 +----------------------------------------------------- +Listed below are the availability of the dishes for the next order! +Dish: chicken rice +Available Dishes: 8 +----------------------------------------------------- +Dish: chicken curry +Available Dishes: 4 +``` + +However, if there is insufficient ingredients in the pantry required for the desired dish quantity, +the following message will be shown, prompting the user to `buy_ingredient` + +```agsl +> add_order name/chicken rice qty/2 +I'm busy crafting your selected dish in the virtual kitchen of your dreams. Bon appétit! ++----------------------------------------+--------------+--------------+ +| Restock | Current | Needed | ++----------------------------------------+--------------+--------------+ +| chicken | 0g | 200g | ++----------------------------------------------------------------------+ +| rice | 0g | 100g | ++----------------------------------------------------------------------+ +Please restock ingredients before preparing the order :) +``` + +### Showing total sales : `list_total_sales` +Displays the dishes sold and total sales for each from Day 1 to the current day that +the cafe is operating on. + +Format: `list_total_sales` + +Example: `list_total_sales` + +Output: +- list_total_sales lists the dishes sold along with the total sales for every operating day of the cafe. +- in this case, the cafe has operated for two days. + +``` ++---------------------------------------------------------------------------+ +| Day 1: | ++----------------------------------------+--------------+-------------------+ +| Dish Name | Dish Qty | Total Cost Price | ++----------------------------------------+--------------+-------------------+ +| Chicken Rice | 2 | 5.00 | +| Chicken Nuggets | 4 | 6.00 | ++---------------------------------------------------------------------------+ +| Total for day: | $11.00 | ++---------------------------------------------------------------------------+ ++---------------------------------------------------------------------------+ +| Day 2: | ++----------------------------------------+--------------+-------------------+ +| Dish Name | Dish Qty | Total Cost Price | ++----------------------------------------+--------------+-------------------+ +| Chicken Rice | 2 | 5.00 | +| Chicken Nuggets | 3 | 4.50 | ++---------------------------------------------------------------------------+ +| Total for day: | $9.50 | ++---------------------------------------------------------------------------+ +``` + +### Showing sales for a chosen day : `list_sale` +Displays the dishes sold along with the total sales for any chosen day. + +Format: `list_sale day/DAY_TO_DISPLAY` + +Example: `list_sale day/2` + +Output: +- list_sale day/2 lists the dishes sold along with the total sales for day 2. + +``` ++---------------------------------------------------------------------------+ +| Day 2: | ++----------------------------------------+--------------+-------------------+ +| Dish Name | Dish Qty | Total Cost Price | ++----------------------------------------+--------------+-------------------+ +| chicken chop | 2 | 4.00 | +| chicken sandwich | 2 | 6.00 | +| chicken noodles | 1 | 2.00 | ++---------------------------------------------------------------------------+ +| Total for day: | $12.00 | ++---------------------------------------------------------------------------+ +``` + +### Advancing to the next day: `next_day` + +Proceeds to the next business day + +Format: `next_day` + +Output: +``` +Prepare for liftoff! We're about to fast-forward to the next day. Hold onto your hats; here we go! +Today is Day 2 +``` + +### Returning to the previous day: `previous_day` + +Goes back to the previous business day + +Format: `previous_day` + +Output: +``` +Sure thing! Let's rev up the virtual DeLorean and take a spin to the previous day. Buckle up, it's time to hit that rewind button! +Today is Day 1 +``` + + +### Exiting the program : `bye` +Exits the program. + +Format: `bye` + +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +## Known Issues +1. The application is unable to decode the data text files if they have been edited in the wrong decoding format. +2. The application is unable to detect wrong argument tag, a general incorrect command format will be printed out for wrong argument tag. +3. The application is unable to support unit conversion, hence only ml and g are accepted as ingredient unit and the use of unit must be constant for the same ingredient. +4. The application is unable to save data for `Menu`, `Pantry` and `OrderList` if it is force exited using Ctrl+C command. +5. The application currently accepts any valid dish name from orders.txt as a valid dish made on a given day, regardless of its presence in the current menu. + - This decision ensures that orders remain intact even if dishes are removed from the menu, allowing for a comprehensive record of all transactions. + - Restricting orders to only those present in the current menu would lead to unintentional deletion of orders containing dishes no longer available. + - Consideration was given to storing the names of dishes in the menu at the time the order was made in orders.txt. + - However, concerns about orders.txt becoming bloated with data were acknowledged. + - As this does not affect the functionality of any aspects of our application nor the logic of functions related to the content stored in orders.txt, we have decided that this can be considered in future improvements. +6. The application is unable to differentiate hash string that has been modified by user in the text file with normal data text. + - This leads the decoder of the text file to attempt to decode the modified hash string, hence resulting in an error message. + - This example illustrates the error message shown when the hash string in pantry_stock.txt is modified to 000000 + - ``` + Well, well, well, looks like someone's been playing with the save files! + Let's keep it classy and use the prescribed format below. + + Format for Pantry_stock.txt: + {Ingredient Name} | {Ingredient Qty} | {Ingredient Unit} + + pantry_stock.txt: Invalid format, this pantry stock will be removed -> 000000 + Done loading pantry_stock.txt! + ------------------------------------------------------------------------ + ``` +7. If the user manually adds an order of dish that does not exist in menu to orders.txt, the order will still be generated but the new dish will not be added to the menu. +8. The decoder of orders.txt will use the string **the last day has no orders but please account for it** as keywords to mark the end of the order history. If the user moves the string, any orders after it will be discarded. + +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ## Command Summary -{Give a 'cheat sheet' of commands here} +| Action | Format, Examples | +|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Add** | `add name/DISH_NAME price/PRICE ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY [, ingredient/INGREDIENT2_NAME qty/INGREDIENT2_QTY, ...]`

Example:
`add name/chicken rice price/3.00 ingredient/rice qty/50g, ingredient/chicken qty/100g` | +| **List Menu** | `list_menu` | +| **List Ingredients** | `list_ingredients dish/DISH_INDEX`

Example:
`list_ingredients index/1` | +| **Delete** | `delete DISH_INDEX`

Example:
`delete 1` | +| **Edit Price** | `edit_price dish/DISH_INDEX price/NEW_PRICE`

Example:
`edit_price dish/1 price/4.50` | +| **List Sale** | `list_total_sales` | +| **List Sale by Day** | `list_sale day/DAY_TO_DISPLAY`

Example:
`list_sale day/1` | +| **View Ingredient Stock** | `view_stock` | +| **Buy Ingredients** | `buy_ingredient ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY[, ingredient/INGREDIENT2_NAME qty/INGREDIENT2_QTY, ...]`

Example
`buy_ingredient ingredient/chicken qty/500g, ingredient/milk qty/1000ml` | +| **Add Order** | `add_order name/DISH_NAME qty/QUANTITY`

Example:
`add_order name/chicken rice qty/2` | +| **Previous Day** | `previous_day` | +| **Next Day** | `next_day` | +| **Help** | `help` | +| **Exit Program** | `bye` | -* Add todo `todo n/TODO_NAME d/DEADLINE` +--------------------------------------------------- + +## Glossary +- **Dish index**: Index of the dish according to `list_menu`. +- **Stock**: The quantity of ingredient available in the pantry of the cafe. +- **Qty**: Quantity \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c9bdc4e030 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,4 @@ +remote_theme: pages-themes/cayman@v0.2.0 +plugins: + - jekyll-remote-theme +gem "github-pages", group: :jekyll_plugins diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml new file mode 100644 index 0000000000..4827e3671d --- /dev/null +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -0,0 +1,31 @@ +@startuml +'https://plantuml.com/deployment-diagram + +actor User + +rectangle { +rectangle Ui +rectangle Parser +rectangle Command +rectangle CafeCtrl +rectangle Data +rectangle Storage +} + +folder TextFiles + +User ..> Ui +Ui --> Parser +Parser --> Command +Parser ..> Data +Command --> Data +Command ..> Ui +Storage --> Data +Storage ...> TextFiles +Ui <- CafeCtrl +CafeCtrl --> Parser +CafeCtrl --> Data +CafeCtrl --> Storage + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/AddOrderCommandClass.puml b/docs/diagrams/class/AddOrderCommandClass.puml new file mode 100644 index 0000000000..43adae3a36 --- /dev/null +++ b/docs/diagrams/class/AddOrderCommandClass.puml @@ -0,0 +1,104 @@ +@startuml +'https://plantuml.com/class-diagram + +title Class Diagram of CafeCtrl `add_order` Command + +CafeCtrl "1" --> "1" UI +CafeCtrl "1" --> "1" Parser +CafeCtrl "1" --> "1" Sales +CafeCtrl "1" --> "1" Pantry +CafeCtrl "1" --> "1" CurrentDate +CafeCtrl "1" o-- "*" Command + +Command <|- AddOrderCommand +Parser "1" --> "*" AddOrderCommand +Parser ..> CurrentDate +Parser ..> Sales +AddOrderCommand --> OrderList +AddOrderCommand "1" --> "1" Chef +AddOrderCommand --> Pantry +AddOrderCommand ..> Order +OrderList --> "*" Order +Sales --> "1...*" OrderList +Chef ..> Pantry + +class CafeCtrl { +- setup() +- run() ++ main(args : String[]) +} + +class Command { + +} + +class UI { +- scanner : Scanner ++ receiveUserInput() : String ++ showToUser() : void ++ showChefMessage() : void ++ showTotalCost(dollarCost : String) : void +} + +class AddOrderCommand { +# pantry : Pantry +# menu : Menu +- ui : Ui +- order : Order ++ execute() : void +} + +class Parser { +{static} + parseCommand(menu : Menu, userInput : String, ui : Ui, pantry : Pantry, sales : Sales, currentDate : CurrentDate) : Command +{static} - prepareOrder(menu : Menu, arguments : String, ui : Ui, pantry : Pantry, sales : Sales, currentDate : CurrentDate) : Command +{static} - setOrderList(currentDate : CurrentDate, sales : Sales) +} + +class OrderList { +- orderList : ArrayList +- totalOrderListCost : float ++ addOrder() : void ++ addCost() : void ++ getTotalCost() : float +} + +class Order { +- orderedDish : Dish +- dishQty : int +- ingredientList : ArrayList +- isComplete : boolean +- totalOrderCost : float +- getDishPrice() : float +- setIngredientList() : ArrayList ++ getIngredientList() : ArrayList ++ getTotalOrderCost() : float ++ setComplete() : void ++ getIsComplete() : boolean +} + +class Chef { +- order : Order +- pantry : Pantry +- ui : Ui ++ cookDish() : void +} + +class Pantry { +- pantryStock : ArrayList +- menuItems : ArrayList +- ui : Ui ++ isDishCooked( :ArrayList) : boolean ++ calculateDishAvailability(menu : Menu) : void +} + +class Sales { +- orderLists : ArrayList ++ getOrderList(index : int) : OrderList +} + +class CurrentDate { +- currentDay : int ++ getCurrentDay() : int +} + +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/Data.puml b/docs/diagrams/class/Data.puml new file mode 100644 index 0000000000..c95dc90ee4 --- /dev/null +++ b/docs/diagrams/class/Data.puml @@ -0,0 +1,104 @@ +@startuml +'https://plantuml.com/class-diagram + +class Menu { ++ getMenuItemsList(): ArrayList ++ getSize(): int ++ getDishFromId(menuID: int): Dish ++ getDishFromName(dishName: String): Dish ++ removeDish(menuID: int): void ++ addDish(dish: Dish): void ++ isValidDishIndex(dishIndex: int): boolean +} + +class Dish { +- name: String +- price: float ++ getName(): String ++ getIngredients(): ArrayList ++ getPrice(): float ++ setPrice(newPrice: float): void +} + +class Ingredient { +- name: String +- qty: int +- unit: String ++ getName(): String ++ getQty(): int ++ getUnit(): String ++ setQty(qty:int): void +} + +class Pantry { ++ getPantryStock():ArrayList ++ addIngredientToStock(name: String, qty: int, unit: String): Ingredient ++ isDishCooked(dishIngredients: ArrayList): boolean +- getIngredient(dishIngredient: Ingredient): Ingredient ++ calculateDishAvailability(menu: Menu): void ++ calculateMaxDishes(dish: Dish, menu: Menu) +- calculateMaxDishForEachIngredient(dishIngredient: Ingredient): int +- handleRestock(dishIngredient: Ingredient): void ++ retrieveIngredientsForDish(orderedDish: String, menu: Menu): ArrayList +} + +class Chef { +- dollarValue: DecimalFormat ++ cookDish(): void +} + +class Order { +- dollarValue: DecimalFormat +- dishQty: int +- isComplete: boolean +- totalOrderCost: float ++ calculateTotalOrderCost(): float +} + +class OrderList { +- dollarValue: DecimalFormat +- HEADER_FORMAT: String +- totalOrderListCost ++ addOrder(order: Order): void ++ addCost(order: Order): void ++ printOrderList(menu: Menu, ui: Ui): void +- aggregateOrder(order: Order, aggregatedOrder: ArrayList): void +- getIndexByDishName(aggregatedOrders: ArrayList, dishName: String): int +- calculateTotalCost(orders: ArrayList): float +} + +class Sales { +- HEADER_FORMAT: String +- daysAccounted: int ++ addOrderList(orderList: OrderList): void ++ nextDay(): void ++ printSales(ui: Ui, menu Menu): void ++ printSaleByDay(ui: Ui, menu: Menu, day: int): void +} + +class CurrentDate { +- currentDay: int ++ nextDay(): void ++ previousDay(): void ++ getCurrentDay(): int +} + +class Ui +note bottom of Ui: Classes in ui package: \nUi, Messages, and ErrorMessages + +Ingredient "*" <--* "1" Dish : ingredients +Dish "*" <--* "1" Menu : menuItems + +Ingredient "*" <--* "1" Pantry : pantryStock +Dish <.. Pantry +Chef "1" <-- Pantry : pantry +Pantry ..> Ui +Chef ..> Ui + +Ingredient "*" <--* "1" Order : ingredientList +Order "*" <--* "1" OrderList : orderList +Order "1" <-- Chef : order +OrderList "*" <--* "1" Sales : orderLists + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/ListMenuCommandClass.puml b/docs/diagrams/class/ListMenuCommandClass.puml new file mode 100644 index 0000000000..3f754f506f --- /dev/null +++ b/docs/diagrams/class/ListMenuCommandClass.puml @@ -0,0 +1,60 @@ +@startuml +'https://plantuml.com/class-diagram + + +title Class Diagram of CafeCtrl `list_menu` Command + +CafeCtrl "1" --> "1" UI +CafeCtrl "1" --> "1" Parser +CafeCtrl "1" --> "1" Menu + +CafeCtrl "1" o-- "*" Command +Command <|- ListMenuCommand +Parser "1" --> "*" ListMenuCommand +ListMenuCommand --> Menu +Menu -- "*" Dish + +class CafeCtrl { +- setup() +- run() ++ main(args : String[]) +} + +class Command { + +} + +class UI { +- scanner : Scanner ++ receiveUserInput() : String ++ showMenuTop() : void ++ showMenuBottom() : void ++ showEmptyMenu() : void ++ showMenuDish(indexNum : String, dishName : String, dishPrice : String) : void +} + +class ListMenuCommand { +- menu : Menu +- ui : Ui ++ execute() : void ++ printEmptyMenu(ui : Ui) : void ++ printFullMenu(menu : Menu, ui : Ui) +} + +class Parser { +{static} + parseCommand(menu : Menu, userInput : String, ui : Ui, pantry : Pantry, orderList : OrderList) : Command +{static} - prepareListMenu(menu : Menu, ui : Ui) : Command +} + +class Menu { +- menuItems : ArrayList ++ getDishFromId(menuID : int) : Dish +} + +class Dish { +- name : String +- price : float ++ getName() : String ++ getPrice() : float +} +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/NextDayCommandClass.puml b/docs/diagrams/class/NextDayCommandClass.puml new file mode 100644 index 0000000000..8cc39df7fb --- /dev/null +++ b/docs/diagrams/class/NextDayCommandClass.puml @@ -0,0 +1,67 @@ +@startuml +'https://plantuml.com/class-diagram + +title Class Diagram of CafeCtrl `next_day` Command + +CafeCtrl "1" --> "1" UI +CafeCtrl "1" --> "1" Parser +CafeCtrl "1" --> "1" Sales +CafeCtrl "1" --> "1" CurrentDate +CafeCtrl "1" o-- "*" Command + +Command <|- NextDayCommand +Parser "1" --> "*" NextDayCommand +NextDayCommand ..> OrderList : Creates > +NextDayCommand --> CurrentDate +NextDayCommand --> Sales +Sales --> "1...*" OrderList + +class CafeCtrl { +- setup() +- run() ++ main(args : String[]) +} + +class Command { + +} + +class UI { +- scanner : Scanner ++ receiveUserInput() : String ++ showToUser() : void ++ printLine() : void ++ showNextDay() : void +} + +class NextDayCommand { +- ui : Ui +- sales : Sales +- currentDate : CurrentDate ++ execute() : void +} + +class Parser { +{static} + parseCommand(menu : Menu, userInput : String, ui : Ui, pantry : Pantry, sales : Sales, currentDate : CurrentDate) : Command +{static} - prepareNextDay(ui : Ui, sales : Sales, currentDate : CurrentDate) : Command +} + +class OrderList { +- orderList : ArrayList +} + +class Sales { +- orderLists : ArrayList +- daysAccounted : int ++ getDaysAccounted() : int ++ addOrderList(orderList : OrderList) ++ nextDay() : void +} + +class CurrentDate { +- currentDay : int ++ getCurrentDay() : int ++ nextDay() : void +} + +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/Parser.puml b/docs/diagrams/class/Parser.puml new file mode 100644 index 0000000000..b715227bfa --- /dev/null +++ b/docs/diagrams/class/Parser.puml @@ -0,0 +1,34 @@ +@startuml +interface ParserUtil { + +parseCommand(): Command +} + +class Parser { + -{static}INPUT_REGEX: String + +parseCommand(): Command + -prepareXYZCommand(): Command + -parsePriceToFloat(priceText: String): float + -parseIngredient(ingredientsListString: String): ArrayList +} + +abstract class Command { + +execute(): void +} + +class XYZCommand { + +execute(): void +} + +class Ui { + +showToUser(textToShow: String): void +} + +CafeCtrl ..> ParserUtil: use +XYZCommand --|> Command +CafeCtrl ..> Command: execute +ParserUtil <|.. Parser +Parser ..> XYZCommand: create +XYZCommand *-- data: use +note left: Classes in data package: Dish, Ingredient, \nCurrentData, Menu, Order, Pantry and Sales +Command *-- Ui: use +@enduml diff --git a/docs/diagrams/class/PreviousDayCommandClass.puml b/docs/diagrams/class/PreviousDayCommandClass.puml new file mode 100644 index 0000000000..78287a6dba --- /dev/null +++ b/docs/diagrams/class/PreviousDayCommandClass.puml @@ -0,0 +1,51 @@ +@startuml +'https://plantuml.com/class-diagram + +title Class Diagram of CafeCtrl `previous_day` Command + +CafeCtrl "1" --> "1" Ui +CafeCtrl "1" --> "1" Parser +CafeCtrl "1" --> "1" CurrentDate +CafeCtrl "1" o-- "*" Command + +Command <|- PreviousDayCommand +Parser "1" --> "*" PreviousDayCommand + +PreviousDayCommand --> CurrentDate + +class CafeCtrl { +- setup() +- run() ++ main(args : String[]) +} + +class Command { + +} + +class Ui { +- scanner : Scanner ++ receiveUserInput() : String ++ showToUser() : void ++ printLine() : void ++ showPreviousDay() : void +} + +class PreviousDayCommand { +- ui : Ui +- currentDate : CurrentDate ++ execute() : void +} + +class Parser { +{static} + parseCommand(menu : Menu, userInput : String, ui : Ui, pantry : Pantry, sales : Sales, currentDate : CurrentDate) : Command +{static} - prepareNextDay(ui : Ui, currentDate : CurrentDate) : Command +} + +class CurrentDate { +- currentDay : int ++ getCurrentDay() : int ++ previousDay() : void +} + +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/Storage.puml b/docs/diagrams/class/Storage.puml new file mode 100644 index 0000000000..2e0edfecde --- /dev/null +++ b/docs/diagrams/class/Storage.puml @@ -0,0 +1,56 @@ +@startuml +'https://plantuml.com/class-diagram + +class Storage { + # fileManager: FileManager + + loadMenu(): Menu + - saveMenu(menu: Menu): void + + loadPantryStock(): Pantry + - savePantryStock(pantry: Pantry): void + + loadOrderList(menu: menu): Sales + - saveOrderList(sales: Sales): void + + saveAll(menu: Menu, sales: Sales, pantry: Pantry): void + + detectTamper(): void + - isFileEmpty(encodedStringArrayList: ArrayList): boolean + - isFileCorrupted(encodedStringArrayList: ArrayList): boolean +} + +class Data +note bottom of Data: Classes in data package: \nMenu, Pantry and Sales + +class Ui + +class FilePath { + + MENU_FILE_PATH: String + + PANTRY_STOCK_FILE_PATH: String + + ORDERS_FILE_PATH: String +} + +class FileManager { + + openTextFile(:FilePath): String + + readTextFile(:FilePath): ArrayList): void + + overwriteFile(:FilePath, :ArrayList): void +} + +class Decoder { + + {static} decodeData(encodedData: ArrayList): Data + + {static} decodeSales(encodedOrderList: ArrayList, menu: Menu): Data +} +note top of Decoder: decodeData function for\nMenu and PantryStock data + +class Encoder { + + {static} encodeData(data: Data): ArrayList +} + +CafeCtrl *-- "1" Storage: initializes + +FileManager "1" --* Storage +Storage ..> Data: stores > +Ui "1" --* FileManager : uses < + +FileManager ..> FilePath: reads from > + +Encoder ..> Data: encodes > + +Decoder ..> Data: decodes > +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/Ui.puml b/docs/diagrams/class/Ui.puml new file mode 100644 index 0000000000..9e01deb3fb --- /dev/null +++ b/docs/diagrams/class/Ui.puml @@ -0,0 +1,32 @@ +@startuml +'https://plantuml.com/class-diagram + +class Ui { ++ OFFSET_LIST_INDEX: int +- scanner: Scanner ++ receiveUserInput(): String ++ showToUser(message: String) ++ printAddDishMessage(dish: Dish): void ++ printIngredients(selectedDish: Dish): void ++ printDeleteMessage(selectedDish: Dish): void ++ showEditPriceMessage(menuItem: String): void ++ showHelp(): void +} + +class Messages +class ErrorMessages +class Parser +class Command +class Main +class Data +class Storage + +Messages <. Ui : prints < +Ui .> ErrorMessages : prints > +Parser ..> Ui : uses > +Ui "1" <-- "*" Command : ui +Ui "1" <-- "1" Main : ui +Ui "1" <-- "*" Data : ui +Ui "1" <-- "*" Storage :ui + +@enduml \ No newline at end of file diff --git a/docs/diagrams/class/ui_singleton.puml b/docs/diagrams/class/ui_singleton.puml new file mode 100644 index 0000000000..3c4c122114 --- /dev/null +++ b/docs/diagrams/class/ui_singleton.puml @@ -0,0 +1,10 @@ +@startuml +'https://plantuml.com/class-diagram + +class Ui << singleton >> { +- {static} theOneUi: Ui +- scanner: Scanner +- Ui(): Ui ++ getInstance(): Ui +} +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/AddDishCommand_execute.puml b/docs/diagrams/sequence/AddDishCommand_execute.puml new file mode 100644 index 0000000000..f2c54838f9 --- /dev/null +++ b/docs/diagrams/sequence/AddDishCommand_execute.puml @@ -0,0 +1,36 @@ +@startuml +'https://plantuml.com/sequence-diagram +autonumber + +-> ":AddDishCommand": execute() +activate ":AddDishCommand" +":AddDishCommand" -> ":Menu": addDish(dish: Dish) +activate ":Menu" +return +":AddDishCommand" -> ":Ui": printAddDishMessage(dish: Dish) + + +activate ":Ui" +":Ui" -> ":Dish": getName() +activate ":Dish" +":Dish" --> ":Ui": dishNameString: String +deactivate ":Dish" +":Ui" -> ":Dish": getPrice() +activate ":Dish" +":Dish" --> ":Ui": dishPrice: float +deactivate ":Dish" + +loop ingredients +":Ui" -> ":Ingredients": toString() +activate ":Ingredients" +":Ingredients" --> ":Ui" +deactivate ":Ingredients" +end loop + +":Ui" -> ":Ui": showToUser(message: String) +activate ":Ui" +return +return +return + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/AddOrderCommand_execute.puml b/docs/diagrams/sequence/AddOrderCommand_execute.puml new file mode 100644 index 0000000000..bea75401c2 --- /dev/null +++ b/docs/diagrams/sequence/AddOrderCommand_execute.puml @@ -0,0 +1,63 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +-> ":AddOrderCommand" : execute() + activate ":AddOrderCommand" + ":AddOrderCommand" -> ":OrderList" : addOrder(order : Order) + activate ":OrderList" + return + ":AddOrderCommand" -> ":Chef" : new Chef(order : Order, pantry : Pantry, ui : Ui) + activate ":Chef" + return chef : Chef + ":AddOrderCommand" -> ":Chef" : cookDish() + activate ":Chef" + opt !order.getIsComplete() + ":Chef" -> ":Ui" : showChefMessage() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return + return + ":Chef" -> ":Order" : getIngredientList() + activate ":Order" + return :ArrayList + ":Chef" -> ":Pantry" : isDishCooked(:ArrayList) + activate ":Pantry" + return isComplete : boolean + ":Chef" -> ":Order" : setComplete(isComplete : boolean) + activate ":Order" + return + end + ":Chef" -> ":Ui" : showOrderStatus(args : String) + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return + return + ":Chef" -> ":Pantry" : calculateDishAvailability(menu : Menu) + activate ":Pantry" + return + return + destroy ":Chef" + alt order.getIsComplete() + ":AddOrderCommand" -> ":OrderList" : addCost(order : Order) + activate ":OrderList" + return + ":AddOrderCommand" -> ":Ui" : showOrderStatus(totalCost : String) + activate ":Ui" + return + else + ":AddOrderCommand" -> ":Order" : getOrderedDish() + activate ":Order" + return orderedDish : Dish + ":AddOrderCommand" -> ":Pantry" : calculateMaxDishes(orderedDish : Dish, menu : Menu, order : Order) + activate ":Pantry" + return + ":AddOrderCommand" -> ":Ui" : showIncompleteOrder() + activate ":Ui" + return + end +return +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/Architecture_Encode_Data.puml b/docs/diagrams/sequence/Architecture_Encode_Data.puml new file mode 100644 index 0000000000..884fc5c0c2 --- /dev/null +++ b/docs/diagrams/sequence/Architecture_Encode_Data.puml @@ -0,0 +1,38 @@ +@startuml +!define UI class ":Ui" +!define MAIN class ":CafeCtrl" +!define STORAGE class ":Storage" +!define DATA class ":Data" +!define PARSER class ":Parser" +!define COMMAND class ":Command" + +autonumber +Actor user + +user -> ":Ui": 'bye' +Activate ":Ui" +":Ui"-> ":CafeCtrl" : receiveUserInput('bye') +loop !exitCommand +Activate ":CafeCtrl" +":CafeCtrl" -> ":Parser" : parseCommand('bye') +Activate ":Parser" +return exitCommand: ":Command" +":CafeCtrl" -> ":Command" : exitCommand.execute() +Activate ":Command" +return +end loop +":CafeCtrl"->":Storage" : saveAll() +Activate ":Storage" +":Storage" -> ":Storage": saveMenu() +Activate ":Storage" +return +":Storage" -> ":Storage": saveOrderList() +Activate ":Storage" +return +":Storage" -> ":Storage": savePantryStock() +Activate ":Storage" +return +return +return + +@enduml diff --git a/docs/diagrams/sequence/BuyIngredientCommand_execute.puml b/docs/diagrams/sequence/BuyIngredientCommand_execute.puml new file mode 100644 index 0000000000..02a9f650f0 --- /dev/null +++ b/docs/diagrams/sequence/BuyIngredientCommand_execute.puml @@ -0,0 +1,48 @@ +@startuml +!define COMMAND class ":BuyIngredientCommand" +!define UI class ":Ui" +!define MENU class ":Menu" +!define PANTRY class ":Pantry" + +autonumber + +-> ":BuyIngredientCommand" : execute() +activate ":BuyIngredientCommand" + +":BuyIngredientCommand" -> ":BuyIngredientCommand" : addIngredient() +activate ":BuyIngredientCommand" +loop i < ingredients.size() +":BuyIngredientCommand" -> ":Pantry" : addIngredientToStock(name: String, qty: int, unit: String) +activate ":Pantry" +":Pantry" -> ":Pantry" : getPantryStock() +activate ":Pantry" +return pantryStock: ArrayList + +":Pantry" -> ":Pantry" : getIndexOfIngredient(name: String) +activate ":Pantry" +return ingredientIndex: int +alt ingredientIndex != -1 +":Pantry" -> ":Pantry" : addIngredientQuantity(qty: int, ingredientIndex: int, unit: String) +activate ":Pantry" +return ingredient: Ingredient +":Pantry" --> ":BuyIngredientCommand" : ingredient: Ingredient +else +return ingredient: Ingredient +end +end loop + +loop i >= 0 +":BuyIngredientCommand" -> ":BuyIngredientCommand" : buildBuyIngredientMessage(ingredient: Ingredient) +activate ":BuyIngredientCommand" +return ingredientString: String +end loop + +return +":BuyIngredientCommand" -> ":Ui" : showToUser(ingredientString: String) +activate ":Ui" +return +return + + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/DeleteDishCommand_execute.puml b/docs/diagrams/sequence/DeleteDishCommand_execute.puml new file mode 100644 index 0000000000..5cb5b077fa --- /dev/null +++ b/docs/diagrams/sequence/DeleteDishCommand_execute.puml @@ -0,0 +1,26 @@ +@startuml +!define COMMAND class ":DeleteDishCommand" +!define UI class ":Ui" +!define MENU class ":Menu" + +autonumber + +-> ":DeleteDishCommand" : execute() +activate ":DeleteDishCommand" + +":DeleteDishCommand" -> ":Menu" : getMenuItemsList().get(dishIndexToBeDeleted: int) +activate ":Menu" +return (selectedDish: Dish) +":DeleteDishCommand" -> ":Ui" : showDeleteMessage(selectedDish: Dish) +activate ":Ui" +":Ui" -> ":Ui" : showToUser(message: String) +activate ":Ui" +return +return +":DeleteDishCommand" -> ":Menu" : removeDish(dishIndexToBeDeleted: int) +activate ":Menu" +return +return + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/EditPriceCommand_execute.puml b/docs/diagrams/sequence/EditPriceCommand_execute.puml new file mode 100644 index 0000000000..3b348f97f7 --- /dev/null +++ b/docs/diagrams/sequence/EditPriceCommand_execute.puml @@ -0,0 +1,32 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber +participant ":Main" as Main +participant ":EditPriceCommand" as EditPriceCommand +participant ":Menu" as Menu +participant ":Dish" as Dish +participant ":Ui" as Ui +participant ":Messages" as Messages + +Main -> EditPriceCommand: execute() +activate EditPriceCommand +EditPriceCommand -> Menu:getDishFromId(menuID: int) +activate Menu +Menu --> EditPriceCommand: (dish: Dish) +deactivate Menu +EditPriceCommand -> Dish: setPrice(newPrice: float) +activate Dish +EditPriceCommand -> Ui: showEditPriceMessages(dishString: String) +deactivate Dish +activate Ui +activate Messages +activate Ui +Ui -> Messages: + PRICE_MODIFIED_MESSAGE: String +Messages --> Ui: "Price modified for the following dish: " +deactivate Messages +Ui -> Ui: showToUser("Price modified for the following dish: ", dishString: String) +deactivate Ui +deactivate EditPriceCommand +deactivate Dish +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/HelpCommand_execute.puml b/docs/diagrams/sequence/HelpCommand_execute.puml new file mode 100644 index 0000000000..e3486543fa --- /dev/null +++ b/docs/diagrams/sequence/HelpCommand_execute.puml @@ -0,0 +1,14 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +":CafeCtrl" -> ":HelpCommand": execute() +activate ":HelpCommand" +":HelpCommand" -> ":Ui": showHelp() +activate ":Ui" +":Ui" -> ":Ui": showToUserWithSpaceBetweenLines(messages: String...) +activate ":Ui" +deactivate ":Ui" + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/ListIngredientCommand_execute.puml b/docs/diagrams/sequence/ListIngredientCommand_execute.puml new file mode 100644 index 0000000000..4a3ba6606a --- /dev/null +++ b/docs/diagrams/sequence/ListIngredientCommand_execute.puml @@ -0,0 +1,61 @@ +@startuml +!define COMMAND class ":ListIngredientCommand" +!define UI class ":Ui" +!define MENU class ":Menu" +!define DISH class ":Dish" +!define PARSER class Parser + +autonumber + + -> ":ListIngredientCommand": execute() + +activate ":ListIngredientCommand" +":ListIngredientCommand" -> ":Menu" : getMenuItemsList() +activate ":Menu" +":Menu" --> ":ListIngredientCommand": selectedDish : ":Dish" +deactivate ":Menu" + +":ListIngredientCommand" -> ":Ui" : showListIngredientsMessage(selectedDish: Dish) +activate ":Ui" + +":Ui" -> ":Ui" : showDishName(dish: Dish) +activate ":Ui" +":Ui" -> ":Dish" : dish.getName() +activate ":Dish" +return : String +return + +":Ui" -> ":Ui" : showIngredientList(dish : Dish) +activate ":Ui" + +":Ui" -> ":Dish" : selectedDish.getIngredients() +activate ":Dish" +return ingredients: ArrayList + + + +loop ingredients +":Ui" -> ":Ingredients": ingredient.getName() +activate ":Ingredients" +":Ingredients" --> ":Ui" :String +deactivate ":Ingredients" +":Ui" -> ":Ingredients": ingredient.getQty() +activate ":Ingredients" +":Ingredients" --> ":Ui" :int +deactivate ":Ingredients" +":Ui" -> ":Ingredients": ingredient.getUnit() +activate ":Ingredients" +":Ingredients" --> ":Ui" :String +deactivate ":Ingredients" +":Ui" -> ":Ui" : formatListIngredient(ingredientName: String, ingredientAmount: String) +activate ":Ui" +return +end loop + +":Ui" -> ":Ui": showIngredientsEndCap() +activate ":Ui" +return +return +return + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/ListMenuCommand_executeSequence.puml b/docs/diagrams/sequence/ListMenuCommand_executeSequence.puml new file mode 100644 index 0000000000..cbbdf0570a --- /dev/null +++ b/docs/diagrams/sequence/ListMenuCommand_executeSequence.puml @@ -0,0 +1,55 @@ +@startuml + +autonumber + + + +-> ":ListMenuCommand" : execute() + activate ":ListMenuCommand" + alt menu.getSize() == 0 + ":ListMenuCommand" -> ":ListMenuCommand" : printEmptyMenu(ui) + ":ListMenuCommand" -> ":Ui" : showEmptyMenu() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(message: String) + activate ":Ui" + return + return + else + ":ListMenuCommand" -> ":ListMenuCommand" : printFullMenu(menu, ui) + ":ListMenuCommand" -> ":Ui" : showMenuTop() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(message: String) + activate ":Ui" + return + return + loop Dish : Menu + ":ListMenuCommand" -> ":Menu" : getDishFromId(id) + activate ":Menu" + ":Menu" --> ":ListMenuCommand" : (selectedDish : Dish) + deactivate ":Menu" + ":ListMenuCommand" -> ":Dish" : selectedDish.getName() + activate ":Dish" + ":Dish" --> ":ListMenuCommand" : (dishName : String) + deactivate ":Dish" + ":ListMenuCommand" -> ":Dish" : selectedDish.getPrice() + activate ":Dish" + ":Dish" --> ":ListMenuCommand" : (dishPrice : String) + deactivate ":Dish" + ":ListMenuCommand" -> ":Ui" : showMenuDish() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(message: String) + activate ":Ui" + return + return + end + ":ListMenuCommand" -> ":Ui" : showMenuBottom() : String) + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(message: String) + activate ":Ui" + return + return + +end +return + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/ListSalesByDayCommand.puml b/docs/diagrams/sequence/ListSalesByDayCommand.puml new file mode 100644 index 0000000000..cb45a87ed0 --- /dev/null +++ b/docs/diagrams/sequence/ListSalesByDayCommand.puml @@ -0,0 +1,74 @@ +@startuml +!define COMMAND class ":ListSaleByDayCommand" +!define SALES class ":Sales" +!define ORDERLIST class ":OrderList" +!define ORDER class ":Order" +!define UI class ":Ui" + +autonumber + + -> ":ListSaleByDayCommand": execute() + +activate ":ListSaleByDayCommand" +":ListSaleByDayCommand" -> ":Sales": sales.printSaleByDay(ui: ":Ui", menu: Menu, day: int) +activate ":Sales" +":Sales" -> ":OrderList": orderList.hasCompleteOrders() + +activate ":OrderList" +return :boolean + +alt no complete order || no orders +":Sales" -> ":Ui" : showToUser(message: String) +activate ":Ui" +return +":Sales" --> ":ListSaleByDayCommand" +<-- ":ListSaleByDayCommand" +end alt + +":Sales" -> ":Ui": showSalesTop() +activate ":Ui" +return +":Sales" -> ":OrderList": orderList.printOrderList() +activate ":OrderList" + +loop order +":OrderList" -> ":OrderList": aggregateOrders() +activate ":OrderList" +return +end loop + +loop aggregatedOrder +":OrderList" -> ":Order" **: +activate ":Order" +return aggregatedOrder: ":Order" +":OrderList" -> ":Order": aggregatedOrder.getDishName() +activate ":Order" +return :String +":OrderList" -> ":Order": aggregatedOrder.getQty() +activate ":Order" +return :String +":OrderList" -> ":Order": aggregatedOrder.getTotalOrderCost() +activate ":Order" +return :float +destroy ":Order" +":OrderList" -> ":Ui": showSalesAll(dishName: String, dishQty: int, dishPrice: String) +activate ":Ui" +":Ui" -> ":Ui": formatShowSales(dishName: String, dishQty: int, dishPrice: String) +activate ":Ui" +return +return +end loop +":OrderList" -> ":Ui": showSalesBottom() +activate ":Ui" +return +":OrderList" -> ":Ui": showSalesCost() +activate ":Ui" +return +":OrderList" -> ":Ui": showSalesBottom() +activate ":Ui" +return +return +return +return + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/NextDayCommand_execute.puml b/docs/diagrams/sequence/NextDayCommand_execute.puml new file mode 100644 index 0000000000..67bcd325d8 --- /dev/null +++ b/docs/diagrams/sequence/NextDayCommand_execute.puml @@ -0,0 +1,44 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +-> ":NextDayCommand" : execute() + activate ":NextDayCommand" + ":NextDayCommand" -> ":Ui" : printLine() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return + return + ":NextDayCommand" -> ":CurrentDate" : nextDay() + activate ":CurrentDate" + return + ":NextDayCommand" -> ":CurrentDate" : getCurrentDay() + activate ":CurrentDate" + return nextDay : int + ":NextDayCommand" -> ":Sales" : getDaysAccounted() + activate ":Sales" + return daysAccounted : int + opt nextDay > daysAccounted + ":NextDayCommand" -> ":OrderList" : new OrderList() + activate ":OrderList" + return newOrderList : OrderList + ":NextDayCommand" -> ":Sales" : addOrderList(newOrderList) + activate ":Sales" + return + ":NextDayCommand" -> ":Sales" : nextDay() + activate ":Sales" + return + end + ":NextDayCommand" -> ":Ui" : showNextDay() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return + return + ":NextDayCommand" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return +return +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/PantryCalculateMaxDish.puml b/docs/diagrams/sequence/PantryCalculateMaxDish.puml new file mode 100644 index 0000000000..40f6fe2612 --- /dev/null +++ b/docs/diagrams/sequence/PantryCalculateMaxDish.puml @@ -0,0 +1,52 @@ +@startuml +!define FUNCTION class calculateMaxDish +!define PANTRY class ":Pantry" +!define INGREDIENT class ":Ingredient" +!define DISH class ":Dish" + +autonumber + +-> ":Pantry": calculateMaxDish(dish: ":Dish", menu: Menu, order: Order) +activate ":Pantry" + +":Pantry" -> ":Dish" : dish.getName() +activate ":Dish" +return dishName:String + +":Pantry" -> ":Pantry" : retrieveIngredientsForDish(dishName: String) +activate ":Pantry" +return dishIngredients: ArrayList<":Ingredient"> + +loop dishIngredients +":Pantry" -> ":Ingredient" **: +activate ":Ingredient" +return dishIngredient: ":Ingredient" + +":Pantry" -> ":Pantry" : calculateMaxDishForEachIngredient(dishIngredient: Ingredient) +activate ":Pantry" +return numOfDish: int + +alt !order.getIsComplete() +":Pantry" -> ":Pantry" : handleIncompleteDishCase(dishIngredient: Ingredient), order: Order, numOfDish: int) +activate ":Pantry" + +alt lacking ingredients (numOfDish < orderQty) +":Pantry" -> ":Pantry": handleRestock(dishIngredient: ":Ingredient", orderQty: int) +activate ":Pantry" +return +end alt +return + +else order.getIsComplete() +":Pantry" -> ":Pantry" : handleZeroDishCase(dishIngredient: Ingredient), order: Order, numOfDish: int) +activate ":Pantry" +alt lacking ingredients (numOfDish == 0) +":Pantry" -> ":Pantry": handleRestock(dishIngredient: ":Ingredient", orderQty: int) +activate ":Pantry" +return +end alt +return +end loop +return maxNumOfDish: int + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/PantryIsDishCooked.puml b/docs/diagrams/sequence/PantryIsDishCooked.puml new file mode 100644 index 0000000000..716042d9cf --- /dev/null +++ b/docs/diagrams/sequence/PantryIsDishCooked.puml @@ -0,0 +1,50 @@ +@startuml +!define FUNCTION class isDishCooked +!define PANTRY class ":Pantry" +!define INGREDIENT class ":Ingredient" +!define DISH class Dish + +autonumber + + -> ":Pantry": isDishCooked(dishIngredients: ArrayList) + activate ":Pantry" + + loop dishIngredients + ":Pantry" -> ":Ingredient" **: + activate ":Ingredient" + ":Ingredient" --> ":Pantry": ingredient: ":Ingredient" + deactivate ":Ingredient" + + ":Pantry" -> ":Ingredient": getQty() + activate ":Ingredient" + return usedQty: int + + ":Pantry" -> ":Pantry": getIngredient(dishIngredient: Ingredient)) + activate ":Pantry" + return :":Ingredient" + + ":Pantry" -> ":Ingredient" **: + activate ":Ingredient" + return usedIngredientFromStock: ":Ingredient" + + alt usedIngredientFromStock == null + <-- ":Pantry": :boolean + end alt + + ":Pantry" -> ":Ingredient": getQty() + activate ":Ingredient" + return stockQty: int + + alt not enough ":Ingredient" (stockQty-usedQty<0) + <-- ":Pantry": :boolean + end alt + ":Pantry" -> ":Ingredient" : setQty(quantity: int) + activate ":Ingredient" + return + end loop + + return :boolean + + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/Parser.puml b/docs/diagrams/sequence/Parser.puml new file mode 100644 index 0000000000..be190e3554 --- /dev/null +++ b/docs/diagrams/sequence/Parser.puml @@ -0,0 +1,29 @@ +@startuml +'https://plantuml.com/sequence-diagram + +Participant ":Ui" as Ui +Participant ":Main" as Main +Participant ":Parser" as Parser +Participant ":XYZCommand" as XYZCommand + +activate Ui +Ui -> Main: fullUserInput: String +activate Main +Main -> Parser: parseCommand(fullUserInput: String) +activate Parser + +alt XYZCOMMAND_WORD +Parser -> Parser: prepareXYZCommand(requiredArguments) +activate Parser +Parser -> XYZCommand: XYZCommand(requiredArguments) +activate XYZCommand +XYZCommand --> Parser: XYZCommand +deactivate XYZCommand +deactivate Parser +end alt + +Parser --> Main: XYZCommand +deactivate Parser +Main -> XYZCommand: execute() +activate XYZCommand +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/PreviousDayCommand_execute.puml b/docs/diagrams/sequence/PreviousDayCommand_execute.puml new file mode 100644 index 0000000000..2a4fc2edc7 --- /dev/null +++ b/docs/diagrams/sequence/PreviousDayCommand_execute.puml @@ -0,0 +1,27 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +-> ":PreviousDayCommand" : execute() + activate ":PreviousDayCommand" + ":PreviousDayCommand" -> ":Ui" : printLine() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return + return + ":PreviousDayCommand" -> ":CurrentDate" : previousDay() + activate ":CurrentDate" + return + ":PreviousDayCommand" -> ":Ui" : showPreviousDay() + activate ":Ui" + ":Ui" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return + return + ":PreviousDayCommand" -> ":Ui" : showToUser(args : String) + activate ":Ui" + return +return +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/ViewTotalStockCommand_execute.puml b/docs/diagrams/sequence/ViewTotalStockCommand_execute.puml new file mode 100644 index 0000000000..a2c96683cd --- /dev/null +++ b/docs/diagrams/sequence/ViewTotalStockCommand_execute.puml @@ -0,0 +1,25 @@ +@startuml +!define COMMAND class ":ViewTotalStockCommand" +!define UI class ":Ui" +!define PANTRY class ":Pantry" + +autonumber + +-> ":ViewTotalStockCommand" : execute() +activate ":ViewTotalStockCommand" + +":ViewTotalStockCommand" -> ":Pantry" : getPantryStock().get(dishIndexToBeDeleted: int) +activate ":Pantry" +return (pantryStock: ArrayList) +loop Ingredient ingredient : pantryStock +":ViewTotalStockCommand" -> ":Ui" : showIngredientStock(name: String, qty: int, unit: String) +activate ":Ui" +":Ui" -> ":Ui" : showToUser(message: String) +activate ":Ui" +return +return +end loop +return + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/sequence/ui_singleton.puml b/docs/diagrams/sequence/ui_singleton.puml new file mode 100644 index 0000000000..08c3436d9d --- /dev/null +++ b/docs/diagrams/sequence/ui_singleton.puml @@ -0,0 +1,17 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber +participant ":Command" as Command +participant "Ui" as Ui <> + +Command -> Ui: getInstance() +alt Ui.theOneUi != null + return theOneui: Ui +else + Ui -> Ui: Ui() + Ui --> Ui: theOneUi: Ui + Ui -> Ui: setTheOneUi(theOneUi: Ui) + Ui -> Command: theOneUi: Ui +end +@enduml \ No newline at end of file diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png new file mode 100644 index 0000000000..2ecaf0f488 Binary files /dev/null and b/docs/images/ArchitectureDiagram.png differ diff --git a/docs/images/aboutUs/dexter.jpg b/docs/images/aboutUs/dexter.jpg new file mode 100644 index 0000000000..0e1723d073 Binary files /dev/null and b/docs/images/aboutUs/dexter.jpg differ diff --git a/docs/images/aboutUs/naychimin.png b/docs/images/aboutUs/naychimin.png new file mode 100644 index 0000000000..5a48a58427 Binary files /dev/null and b/docs/images/aboutUs/naychimin.png differ diff --git a/docs/images/aboutUs/shanice.jpg b/docs/images/aboutUs/shanice.jpg new file mode 100644 index 0000000000..1593e26bdb Binary files /dev/null and b/docs/images/aboutUs/shanice.jpg differ diff --git a/docs/images/aboutUs/zhongheng.jpg b/docs/images/aboutUs/zhongheng.jpg new file mode 100644 index 0000000000..9aff6000ce Binary files /dev/null and b/docs/images/aboutUs/zhongheng.jpg differ diff --git a/docs/images/aboutUs/ziyi.jpg b/docs/images/aboutUs/ziyi.jpg new file mode 100644 index 0000000000..cc716fecca Binary files /dev/null and b/docs/images/aboutUs/ziyi.jpg differ diff --git a/docs/images/class/AddOrderCommandClass.png b/docs/images/class/AddOrderCommandClass.png new file mode 100644 index 0000000000..0615cee9ca Binary files /dev/null and b/docs/images/class/AddOrderCommandClass.png differ diff --git a/docs/images/class/Data.png b/docs/images/class/Data.png new file mode 100644 index 0000000000..5fe2f771ca Binary files /dev/null and b/docs/images/class/Data.png differ diff --git a/docs/images/class/ListMenuCommandClass.png b/docs/images/class/ListMenuCommandClass.png new file mode 100644 index 0000000000..2119f14c07 Binary files /dev/null and b/docs/images/class/ListMenuCommandClass.png differ diff --git a/docs/images/class/NextDayCommandClass.png b/docs/images/class/NextDayCommandClass.png new file mode 100644 index 0000000000..ba3adefc2d Binary files /dev/null and b/docs/images/class/NextDayCommandClass.png differ diff --git a/docs/images/class/Parser.svg b/docs/images/class/Parser.svg new file mode 100644 index 0000000000..11d4f2eb92 --- /dev/null +++ b/docs/images/class/Parser.svg @@ -0,0 +1 @@ +ParserUtilparseCommand(): CommandParserINPUT_REGEX: StringparseCommand(): CommandprepareXYZCommand(): CommandparsePriceToFloat(priceText: String): floatparseIngredient(ingredientsListString: String): ArrayList<Ingredient>Commandexecute(): voidXYZCommandexecute(): voidUishowToUser(textToShow: String): voidCafeCtrldataClasses in data package: Dish, Ingredient,CurrentData, Menu, Order, Pantry and Salesuseexecutecreateuseuse \ No newline at end of file diff --git a/docs/images/class/PreviousDayCommandClass.png b/docs/images/class/PreviousDayCommandClass.png new file mode 100644 index 0000000000..769fb2400c Binary files /dev/null and b/docs/images/class/PreviousDayCommandClass.png differ diff --git a/docs/images/class/Storage.png b/docs/images/class/Storage.png new file mode 100644 index 0000000000..343073aa92 Binary files /dev/null and b/docs/images/class/Storage.png differ diff --git a/docs/images/class/Ui.png b/docs/images/class/Ui.png new file mode 100644 index 0000000000..2e4ba77722 Binary files /dev/null and b/docs/images/class/Ui.png differ diff --git a/docs/images/class/ui_singleton.png b/docs/images/class/ui_singleton.png new file mode 100644 index 0000000000..044c34001e Binary files /dev/null and b/docs/images/class/ui_singleton.png differ diff --git a/docs/images/sequence/AddDishCommand_execute.png b/docs/images/sequence/AddDishCommand_execute.png new file mode 100644 index 0000000000..741cd1c8d1 Binary files /dev/null and b/docs/images/sequence/AddDishCommand_execute.png differ diff --git a/docs/images/sequence/AddOrderCommand_execute.png b/docs/images/sequence/AddOrderCommand_execute.png new file mode 100644 index 0000000000..9326ecbaa2 Binary files /dev/null and b/docs/images/sequence/AddOrderCommand_execute.png differ diff --git a/docs/images/sequence/Architecture_Encode_Data.png b/docs/images/sequence/Architecture_Encode_Data.png new file mode 100644 index 0000000000..0da4335ab0 Binary files /dev/null and b/docs/images/sequence/Architecture_Encode_Data.png differ diff --git a/docs/images/sequence/BuyIngredientCommand_execute.png b/docs/images/sequence/BuyIngredientCommand_execute.png new file mode 100644 index 0000000000..9ec6dfea84 Binary files /dev/null and b/docs/images/sequence/BuyIngredientCommand_execute.png differ diff --git a/docs/images/sequence/DeleteDishCommand_execute.png b/docs/images/sequence/DeleteDishCommand_execute.png new file mode 100644 index 0000000000..78aaa2f80a Binary files /dev/null and b/docs/images/sequence/DeleteDishCommand_execute.png differ diff --git a/docs/images/sequence/EditPriceCommand_execute.png b/docs/images/sequence/EditPriceCommand_execute.png new file mode 100644 index 0000000000..a6c1422238 Binary files /dev/null and b/docs/images/sequence/EditPriceCommand_execute.png differ diff --git a/docs/images/sequence/HelpCommand_execute.png b/docs/images/sequence/HelpCommand_execute.png new file mode 100644 index 0000000000..f3a9c39eee Binary files /dev/null and b/docs/images/sequence/HelpCommand_execute.png differ diff --git a/docs/images/sequence/HelpCommand_execute.svg b/docs/images/sequence/HelpCommand_execute.svg new file mode 100644 index 0000000000..e9e5a4cdbb --- /dev/null +++ b/docs/images/sequence/HelpCommand_execute.svg @@ -0,0 +1 @@ +:CafeCtrl:CafeCtrl:HelpCommand:HelpCommand:Ui:Ui1execute()2showHelp()3showToUserWithSpaceBetweenLines(messages: String...) \ No newline at end of file diff --git a/docs/images/sequence/ListIngredientCommand_execute.svg b/docs/images/sequence/ListIngredientCommand_execute.svg new file mode 100644 index 0000000000..0da1de38d6 --- /dev/null +++ b/docs/images/sequence/ListIngredientCommand_execute.svg @@ -0,0 +1 @@ +:ListIngredientCommand:ListIngredientCommand:Menu:Menu:Ui:Ui:Dish:Dish:Ingredients:Ingredients1execute()2getMenuItemsList()3selectedDish : ":Dish"4showListIngredientsMessage(selectedDish: Dish)5showDishName(dish: Dish)6dish.getName()7: String8 9showIngredientList(dish : Dish)10selectedDish.getIngredients()11ingredients: ArrayList<Ingredient>loop[ingredients]12ingredient.getName()13String14ingredient.getQty()15int16ingredient.getUnit()17String18formatListIngredient(ingredientName: String, ingredientAmount: String)19 20showIngredientsEndCap()21 22 23  \ No newline at end of file diff --git a/docs/images/sequence/ListMenuCommandSequence.png b/docs/images/sequence/ListMenuCommandSequence.png new file mode 100644 index 0000000000..fbed8f2660 Binary files /dev/null and b/docs/images/sequence/ListMenuCommandSequence.png differ diff --git a/docs/images/sequence/ListMenuCommand_execute.png b/docs/images/sequence/ListMenuCommand_execute.png new file mode 100644 index 0000000000..bdb1496d94 Binary files /dev/null and b/docs/images/sequence/ListMenuCommand_execute.png differ diff --git a/docs/images/sequence/ListSaleByDayCommand_execute.svg b/docs/images/sequence/ListSaleByDayCommand_execute.svg new file mode 100644 index 0000000000..8b23c9636d --- /dev/null +++ b/docs/images/sequence/ListSaleByDayCommand_execute.svg @@ -0,0 +1 @@ +:ListSaleByDayCommand:ListSaleByDayCommand:Sales:Sales:OrderList:OrderList:Ui:Ui:Order1execute()2sales.printSaleByDay(ui: ":Ui", menu: Menu, day: int)3orderList.hasCompleteOrders()4:booleanalt[no complete order || no orders]5showToUser(message: String)6 7 8 9showSalesTop()10 11orderList.printOrderList()loop[order]12aggregateOrders()13 loop[aggregatedOrder]14 :Order15aggregatedOrder: ":Order"16aggregatedOrder.getDishName()17:String18aggregatedOrder.getQty()19:String20aggregatedOrder.getTotalOrderCost()21:float22showSalesAll(dishName: String, dishQty: int, dishPrice: String)23formatShowSales(dishName: String, dishQty: int, dishPrice: String)24 25 26showSalesBottom()27 28showSalesCost()29 30showSalesBottom()31 32 33 34  \ No newline at end of file diff --git a/docs/images/sequence/NextDayCommand_execute.png b/docs/images/sequence/NextDayCommand_execute.png new file mode 100644 index 0000000000..2daefd13b4 Binary files /dev/null and b/docs/images/sequence/NextDayCommand_execute.png differ diff --git a/docs/images/sequence/Pantry_CalculateMaxDish.svg b/docs/images/sequence/Pantry_CalculateMaxDish.svg new file mode 100644 index 0000000000..884067b51f --- /dev/null +++ b/docs/images/sequence/Pantry_CalculateMaxDish.svg @@ -0,0 +1 @@ +:Pantry:Pantry:Dish:Dish:Ingredient1calculateMaxDish(dish: ":Dish", menu: Menu, order: Order)2dish.getName()3dishName:String4retrieveIngredientsForDish(dishName: String)5dishIngredients: ArrayList<":Ingredient">6 :Ingredient7dishIngredient: ":Ingredient"8calculateMaxDishForEachIngredient(dishIngredient: Ingredient)9numOfDish: intalt[!order.getIsComplete()]10handleIncompleteDishCase(dishIngredient: Ingredient), order: Order, numOfDish: int)alt[lacking ingredients (numOfDish < orderQty)]11handleRestock(dishIngredient: ":Ingredient", orderQty: int)12 13 [order.getIsComplete()]14handleZeroDishCase(dishIngredient: Ingredient), order: Order, numOfDish: int)alt[lacking ingredients (numOfDish == 0)]15handleRestock(dishIngredient: ":Ingredient", orderQty: int)16 17 18maxNumOfDish: int \ No newline at end of file diff --git a/docs/images/sequence/Pantry_IsDishCooked.svg b/docs/images/sequence/Pantry_IsDishCooked.svg new file mode 100644 index 0000000000..9f7da4b2eb --- /dev/null +++ b/docs/images/sequence/Pantry_IsDishCooked.svg @@ -0,0 +1 @@ +:Pantry:Pantry:Ingredient1isDishCooked(dishIngredients: ArrayList<Ingredient>)loop[dishIngredients]2 :Ingredient3ingredient: ":Ingredient"4getQty()5usedQty: int6getIngredient(dishIngredient: Ingredient))7:":Ingredient"8 :Ingredient9usedIngredientFromStock: ":Ingredient"alt[usedIngredientFromStock == null]10:boolean11getQty()12stockQty: intalt[not enough ":Ingredient" (stockQty-usedQty<0)]13:boolean14setQty(quantity: int)15 16:boolean \ No newline at end of file diff --git a/docs/images/sequence/Parser.png b/docs/images/sequence/Parser.png new file mode 100644 index 0000000000..3018c1fae0 Binary files /dev/null and b/docs/images/sequence/Parser.png differ diff --git a/docs/images/sequence/Parser.svg b/docs/images/sequence/Parser.svg new file mode 100644 index 0000000000..ee63bab6b9 --- /dev/null +++ b/docs/images/sequence/Parser.svg @@ -0,0 +1 @@ +:Ui:Ui:Main:Main:Parser:Parser:XYZCommand:XYZCommandfullUserInput: StringparseCommand(fullUserInput: String)alt[XYZCOMMAND_WORD]prepareXYZCommand(requiredArguments)XYZCommand(requiredArguments)XYZCommandXYZCommandexecute() \ No newline at end of file diff --git a/docs/images/sequence/ParserParseCommand.png b/docs/images/sequence/ParserParseCommand.png new file mode 100644 index 0000000000..c89809324a Binary files /dev/null and b/docs/images/sequence/ParserParseCommand.png differ diff --git a/docs/images/sequence/Parser_execute_command.svg b/docs/images/sequence/Parser_execute_command.svg new file mode 100644 index 0000000000..53e7bac823 --- /dev/null +++ b/docs/images/sequence/Parser_execute_command.svg @@ -0,0 +1 @@ +UserUser:Main:Main:Parser:Parser:XYZCommand:XYZCommandfullUserInput: StringparseCommand(fullUserInput: String)alt[XYZCOMMAND_WORD]prepareXYZCommand(requiredArguments)XYZCommand(requiredArguments)XYZCommandXYZCommandexecute() \ No newline at end of file diff --git a/docs/images/sequence/PreviousDayCommand_execute.png b/docs/images/sequence/PreviousDayCommand_execute.png new file mode 100644 index 0000000000..f47f9b40d8 Binary files /dev/null and b/docs/images/sequence/PreviousDayCommand_execute.png differ diff --git a/docs/images/sequence/ViewTotalStockCommand_execute.png b/docs/images/sequence/ViewTotalStockCommand_execute.png new file mode 100644 index 0000000000..4fd70f2f37 Binary files /dev/null and b/docs/images/sequence/ViewTotalStockCommand_execute.png differ diff --git a/docs/images/sequence/ui_singleton.png b/docs/images/sequence/ui_singleton.png new file mode 100644 index 0000000000..99319e7f9b Binary files /dev/null and b/docs/images/sequence/ui_singleton.png differ diff --git a/docs/images_PPP/dexter/division_of_labour.png b/docs/images_PPP/dexter/division_of_labour.png new file mode 100644 index 0000000000..b93dc7c624 Binary files /dev/null and b/docs/images_PPP/dexter/division_of_labour.png differ diff --git a/docs/images_PPP/dexter/division_of_labour1.png b/docs/images_PPP/dexter/division_of_labour1.png new file mode 100644 index 0000000000..969e611279 Binary files /dev/null and b/docs/images_PPP/dexter/division_of_labour1.png differ diff --git a/docs/images_PPP/dexter/project_discussion1.png b/docs/images_PPP/dexter/project_discussion1.png new file mode 100644 index 0000000000..0f6cdbe683 Binary files /dev/null and b/docs/images_PPP/dexter/project_discussion1.png differ diff --git a/docs/images_PPP/dexter/project_discussion2.png b/docs/images_PPP/dexter/project_discussion2.png new file mode 100644 index 0000000000..c13ef6fd78 Binary files /dev/null and b/docs/images_PPP/dexter/project_discussion2.png differ diff --git a/docs/images_PPP/naychi/exampleOfBugReport.png b/docs/images_PPP/naychi/exampleOfBugReport.png new file mode 100644 index 0000000000..63ea3168ca Binary files /dev/null and b/docs/images_PPP/naychi/exampleOfBugReport.png differ diff --git a/docs/images_PPP/shanice/bug_reporting.png b/docs/images_PPP/shanice/bug_reporting.png new file mode 100644 index 0000000000..1a1638a777 Binary files /dev/null and b/docs/images_PPP/shanice/bug_reporting.png differ diff --git a/docs/images_PPP/shanice/deleting_unused_method.png b/docs/images_PPP/shanice/deleting_unused_method.png new file mode 100644 index 0000000000..0e43a1b667 Binary files /dev/null and b/docs/images_PPP/shanice/deleting_unused_method.png differ diff --git a/docs/images_PPP/shanice/deleting_unused_method2.png b/docs/images_PPP/shanice/deleting_unused_method2.png new file mode 100644 index 0000000000..10aa036e54 Binary files /dev/null and b/docs/images_PPP/shanice/deleting_unused_method2.png differ diff --git a/docs/images_PPP/shanice/ensure_permission_given.png b/docs/images_PPP/shanice/ensure_permission_given.png new file mode 100644 index 0000000000..0fd77be4ae Binary files /dev/null and b/docs/images_PPP/shanice/ensure_permission_given.png differ diff --git a/docs/images_PPP/zhongheng/Assist_workflow.png b/docs/images_PPP/zhongheng/Assist_workflow.png new file mode 100644 index 0000000000..73d66329a2 Binary files /dev/null and b/docs/images_PPP/zhongheng/Assist_workflow.png differ diff --git a/docs/images_PPP/zhongheng/Bug_reporting.png b/docs/images_PPP/zhongheng/Bug_reporting.png new file mode 100644 index 0000000000..f0fdc59c25 Binary files /dev/null and b/docs/images_PPP/zhongheng/Bug_reporting.png differ diff --git a/docs/images_PPP/zhongheng/Pantry_load_from_file_bug.png b/docs/images_PPP/zhongheng/Pantry_load_from_file_bug.png new file mode 100644 index 0000000000..29c905330d Binary files /dev/null and b/docs/images_PPP/zhongheng/Pantry_load_from_file_bug.png differ diff --git a/docs/images_PPP/zhongheng/Pantry_restock_bug.png b/docs/images_PPP/zhongheng/Pantry_restock_bug.png new file mode 100644 index 0000000000..926a1df828 Binary files /dev/null and b/docs/images_PPP/zhongheng/Pantry_restock_bug.png differ diff --git a/docs/images_PPP/ziyi/relevant_telagram_screenshot_4.png b/docs/images_PPP/ziyi/relevant_telagram_screenshot_4.png new file mode 100644 index 0000000000..1f3fe08733 Binary files /dev/null and b/docs/images_PPP/ziyi/relevant_telagram_screenshot_4.png differ diff --git a/docs/images_PPP/ziyi/relevant_telegram_screenshot_2.png b/docs/images_PPP/ziyi/relevant_telegram_screenshot_2.png new file mode 100644 index 0000000000..58125816b1 Binary files /dev/null and b/docs/images_PPP/ziyi/relevant_telegram_screenshot_2.png differ diff --git a/docs/images_PPP/ziyi/relevant_telegram_screenshot_3.png b/docs/images_PPP/ziyi/relevant_telegram_screenshot_3.png new file mode 100644 index 0000000000..dc85e6bfbe Binary files /dev/null and b/docs/images_PPP/ziyi/relevant_telegram_screenshot_3.png differ diff --git a/docs/images_PPP/ziyi/telegram_chef_text.png b/docs/images_PPP/ziyi/telegram_chef_text.png new file mode 100644 index 0000000000..732c31c6fd Binary files /dev/null and b/docs/images_PPP/ziyi/telegram_chef_text.png differ diff --git a/docs/images_PPP/ziyi/tp_howto.png b/docs/images_PPP/ziyi/tp_howto.png new file mode 100644 index 0000000000..1f9369ab1a Binary files /dev/null and b/docs/images_PPP/ziyi/tp_howto.png differ diff --git a/docs/team/cazh1.md b/docs/team/cazh1.md new file mode 100644 index 0000000000..cf8519e187 --- /dev/null +++ b/docs/team/cazh1.md @@ -0,0 +1,92 @@ +### Zhong Heng - Project Portfolio Page +**Project: CafeCRTL** + +**Overview** - CaféCTRL is a software engineering project for CS2113. The user interacts with the application through CLI. It has around 6 kLoC, and it is done in a team of 5. + +**Value Proposition** - CaféCRTL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers Café proprietors to streamline inventory and menu management. + +**Target User** - Café proprietors who prefer typing on CLI than any other interaction method and are seeking for a software solution to optimize the management of their café's operations. + +### Summary of Contributions + +**Code Contribution** - [Follow here to see code written by me](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=cazh1&breakdown=true) + +#### Feature implemented + +1. **List Menu**
+ Function: Allow user to view all dishes added to menu
+ Command Format: `list_menu` +
+2. **Add Order**
+ Function: Adds an order consisting of dishes off the menu to an order list
+ Command Format: `add_order name/DISH_NAME qty/DISH_QTY`
+ Error Handling: This command is able to detect missing argument tag, missing argument, wrong argument type, empty argument type. It will then output specific error message to prompt the user to enter the correct Command format.
+ Highlights: This feature required the creation of 5 classes to work together to perform the intended task. This really tested my understanding of OOP and planning to minimise overlap and manage the interactions between these classes. +
+3. **Day Change Commands** - Added the ability to traverse through different days.
+ Function: Next Day and Previous Day commands allow the user to traverse through different days to capture sales, simulating a sales system used in a restaurant.
+ Command Format: `next_day` and `previous_day`
+ Error Handling: Next Day command ensures that the intended traversed day has a proper orderList, preventing a NullPointerException. Previous Day command will prevent the user from traversing to days before Day 1.
+ Highlights: This feature required strong understanding of ArrayLists as it worked with multiple layers of ArrayLists as well as different copies of the same ArrayList that needed to be synchronised. +
+4. **Hashing text save files** - Implemented Hashing for text files used to save app user input data.
+ What it does: Hashes the text files generated from user actions (such as Menu, Sales and PantryStock), that will be accessed to load relevant data back into the application. When tampering has been detected, the syntax is shown to the user to ensure that the user is able to adjust the data saved while maintaining the format that the application is able to process.
+ Justification: This was done to detect tampering of these save files which may potentially crash or induce unexpected behaviour from the application when the data is read and loaded into the application.
+ Highlights: This feature relied on knowledge learnt in CS2040C, Data Structures and Algorithms, to come up with the idea, understand how hashing works and how to implement this feature.
+ Credits: https://www.geeksforgeeks.org/java-string-hashcode-method-with-examples/
+ Implemented in PRs: [#283](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/283), [#324](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/324) + +#### Enhancements Implemented +1. **Main**
+ Implemented skeleton code for Main for other teammates to use. +
+2. **Parser**
+ Implemented skeleton class for Parser for other teammates to use. I refactored Parser such that only relevant parameters are passed into commands when executing. This removes bloat from the Command class and leaves the work to the Parser class. +
+3. **Command**
+ Implemented skeleton class for Command for other teammates to use. I refactored existing Command classes such that only relevant parameters are passed into commands when executing. +
+4. **Order**
+ Implemented class for Order, OrderList, Sales and Chef. These classes are accessed by Pantry and Menu for features relating to Orders and Sales, accounting for 1/3 of the application features. +
+5. **Printing**
+ Implemented the printing style of auto formatting box used by `list_menu`, `view_stock` and `show_sales`. +
+6. **Order**
+ Implemented class for CurrentDate. This class is used to facilitate the day changes and sales. +
+ +#### Contributions to UG +[UserGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html) + +1. List Menu +2. Add Order +3. Next Day +4. Previous Day + +#### Contributions to DG +1. List Menu +2. Add Order +3. Next Day +4. Previous Day + +#### Other Contributions to Team-based Task +1. Maintain issue tracker +2. Bug testing for the application and providing detailed steps to recreate found bugs.
+ ![Bugs Found](../images_PPP/zhongheng/Pantry_load_from_file_bug.png) + ![Bugs Found](../images_PPP/zhongheng/Bug_reporting.png) + ![Bugs Found](../images_PPP/zhongheng/Pantry_restock_bug.png) +3. Implemented bug fixes for PED bugs + [#212](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/212), + [#278](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/278), + [#297](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/297) +#### Review/Mentoring Contributions +1. Reviewed and approved PRs. + Some examples of PR reviewed: [#321](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/321), + [#296](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/296), + [#57](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/57), + [#96](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/96) +2. Assist teammates with their acclimation to the project's workflow, particularly using GitHub's branching workflow.
+ ![Assist_workflow](../images_PPP/zhongheng/Assist_workflow.png) +#### Contributions beyond the Project Team +1. Reported a total of [16 program bugs](https://github.com/Cazh1/ped/issues) for another team during the module's PED. diff --git a/docs/team/dexthechik3n.md b/docs/team/dexthechik3n.md new file mode 100644 index 0000000000..9f28c2f2c9 --- /dev/null +++ b/docs/team/dexthechik3n.md @@ -0,0 +1,60 @@ +# Dexter Hoon's Project Portfolio Page +## Project: CafeCRTL + +----------------------------------------------------------------------------------------------- +### **Overview** +CaféCTRL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform also empowers users to streamline stock inventory, menu and orders. The user interacts with it using a CLI. It is written in Java, and has about 6 kLoC. + +----------------------------------------------------------------------------------------------- + +## **Summary of Contributions** + +### Code Contribution +[RepoSense Link](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=DextheChik3n&tabRepo=AY2324S1-CS2113-T17-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Enhancements implemented +#### Feature +* **Add Dish**
+ Function: To allow the user to add a dish with the price and its ingredients to the menu
+ * The most crucial feature in the application in order for all the other features to perform their functions, which had to handle many arguments such as the names, price and quantity. +* **Exit**
+ Function: To exit from the application + * Implemented the backend logic in the main class in order to detect the user entered the `bye` keyword in order to exit from the application which was important to allow the storage to text file functionality to work + +### Enhancement +* Created the methods for reading and writing files in FileManager.java +* Created the regex patterns for most of the command features
+ * Required effort in learning how to format the regex string in order to get the specific intended user inputs +* Created a ParserException class to allow for the use of handling specific errors
+e.g checking for repeated dish/ingredient name, invalid price type, missing arguments, etc. + +### Contributions to the UG +* Added documentation for the features: "add dish" and "exit" application +* Added "Quick Start", "Notes about command format" and "Known Issues" section + +### Contributions to the DG +* Add implementation for "Add dish" feature +* Add implementation for the Storage class + +### Contributions to team-based tasks +* Setting up the GitHub team org/repo +* Created the main `CafeCtrl` class and the basic data objects such as `Menu`, `Ingredient` and `Dish` +* Maintaining a bit of Gradle file +* Maintaining the issue tracker, specifically creating the tags and assigning issues to team members with Zi Yi +* Managed releases v1.0, v2.0 +* Approving and merging PRs + +### Review/mentoring contributions +* Examples of PRs reviewed: +[#98](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/98), [#119](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/119), [#190](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/190) +* Managed the project team discussions by providing a meeting outline
+ Screenshot of Project Outline 1 +![Screenshot of Project Outline 2](../images_PPP/dexter/project_discussion2.png)
+* Provided a summary of the division of labour after meetings
+* Telegram Screenshot + Notion Screenshot +
+ +### Contributions beyond the project team +Reported bugs and suggestions for other teams in the class
+(example: [#19](https://github.com/nus-cs2113-AY2324S1/tp/pull/19)) 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/naychimin.md b/docs/team/naychimin.md new file mode 100644 index 0000000000..9f0edd6445 --- /dev/null +++ b/docs/team/naychimin.md @@ -0,0 +1,95 @@ +### Saung Naychi Min - Project Portfolio Page +**Project: CafeCRTL** + +**Overview** - CaféCTRL is a software engineering project for CS2113. The user interacts with the application through CLI. It has around 6 kLoC, and it is done in a team of 5. + +**Value Proposition** - CaféCRTL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers Café proprietors to streamline inventory and menu management. + +**Target User** - Café proprietors who prefer typing on CLI than any other interaction method and are seeking for a software solution to optimize the management of their café's operations. + +### Summary of Contributions + +### Code Contribution +[Click here to see my code contribution!](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=functional-code~test-code~docs&since=2023-09-22&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=NaychiMin&tabRepo=AY2324S1-CS2113-T17-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code~test-code~docs&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) +### Enhancements implemented + +#### Feature + +1. **List Ingredient** + - Function: Allow the user to view the ingredients of the desired dish from the menu. + - Command Format: `list_ingredient dish/INDEX_OF_DISH_TO_LIST` + - Error Handling: If the specified index is out of range, of a wrong argument type or is empty. +2. **List Total Sales** + - Function: Allow the user to view the sale for each day across every day since the cafe has operated. + - Command Format: `list_total_sales` + - Error Handling: If the command has unnecessary arguments after the command. +3. **Show Sale By Day** + - Function: Allow the user to view the sale for the desired day.
+ - Command Format: `list_sale day/DAY_TO_LIST`
+ - Error Handling: If the specified index is out of range, of a wrong argument type, is empty or the argument tag is missing. +4. **Data processing of 'add_order'** + - My group mate (Cazh1) did the parsing of the command, along with the implementation of needed classes such as Order, OrderList and Chef. + - My role was to seamlessly handle the logic of the data being processed after an order was added to an orderList for the following purposes: + - Order completion determination and Restocking ingredient identification + +#### Enhancements +1. **Pantry Class** + - Collaborated with my group mate (ShaniceTang) to develop the Pantry class. + - ShaniceTang focused on the buying and restocking of pantry ingredients, as detailed in her PPP. + - My role, outlined in point 4 above, involved implementing key functions, including: + - `isDishCooked`: + - Implemented to determine the success of an order. + - The accurate execution of this function is crucial for the overall success of order processing as it affects other operations of the cafe, such as the amount of total sales to be displayed to users. + - `calculateDishAvailability`: + - Informs the user of the available quantity for each dish in the menu after each order. + - Provides essential insights into the real-time status of dish availability, alerting users of the availability of each dish in the menu. + - `calculateMaxDishes`: + - Handles the logic for calculating the number of dishes made. + - Manages the complex logic for determining restocked ingredients and their required quantities. + - The implementations as stated above served as the link between the add_order and buy_ingredients commands unified the data(ingredients) processing aspect of order management. + - Order Processing: Seamlessly integrating logic for order success determination and the need for Pantry's ingredient stock management. + - Pantry Stock Management: My active contribution to the Pantry class connected the use of add_order command with subsequent use of the buy_ingredients command, making it a central hub for order processing, dish availability checks, and ingredient restocking prompts. + +2. **Encoding of Sales** + - Implemented encoding for the Sales object, involving: + - Parsing through various attributes of the Sales object using the delimiter `|`. + - Storing the data in a text file. + +3. **Decoding of Sales** + - Executed decoding for the Sales object, encompassing: + - Parsing through the text file and separating contents using the delimiter `|`. + - Using parsed attributes to instantiate the Sales object for use in other command classes. + - Implemented error handling during decoding: lines with missing delimiters or incorrect formatting in the text file are filtered out (collaboration with Cazh1). + +4. **Parser** + - Implemented parsing and error handling for the commands listed in the section above. + +### Contributions to UG +[UserGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html) + +1. [List Ingredients](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html#listing-ingredients-needed-for-the-selected-dish--list_ingredients) +2. [List Total Sales](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html#showing-total-sales--list_total_sales) +3. [List Sale by day](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html#showing-sales-for-a-chosen-day--list_sale) + +### Contributions to DG +[DeveloperGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html) +1. Worked with ShaniceTang on the following: + - [Architecture diagram](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html#architecture) and overall description of the architecture. + - Drafting `Data` Component class diagram and [description](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html#data-component) +2. [List Ingredient](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html#list-ingredients) +3. [List Sale By Day](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html#list-sale-by-day) +4. [Pantry - isDishCooked()](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html#pantry---isdishcooked) +5. [Pantry - calculateMaxDish()](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html#pantry---calculatemaxdish) + +### Other Contributions to Team-based Task +1. Maintaining the issue tracker +2. Testing of application and reporting bugs found. Example shown below:
+ Telegram Screenshot + +### Review / Mentoring Contributions +1. Reviewed and merged some PRs such as [#313](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/313), ... +2. Consulting the group when clarification is needed and actively participating in weekly meetings. + +### Contributions Beyond the Project Team +1. Reported bugs in other teams' application: [#157](https://github.com/AY2324S1-CS2113T-W11-2/tp/issues/157), [#141](https://github.com/AY2324S1-CS2113T-W11-2/tp/issues/141), [#172](https://github.com/AY2324S1-CS2113T-W11-2/tp/issues/172), [#145](https://github.com/AY2324S1-CS2113T-W11-2/tp/issues/145) +2. Reviewed other groups tp PRs : [#11](https://github.com/nus-cs2113-AY2324S1/tp/pull/11/files) diff --git a/docs/team/shanicetang.md b/docs/team/shanicetang.md new file mode 100644 index 0000000000..ecf6ccb876 --- /dev/null +++ b/docs/team/shanicetang.md @@ -0,0 +1,81 @@ +# Shanice Tang - Project Portfolio Page + +**Project: CafeCRTL** +----------------------------------------------------------------------------------------------- +## **Overview** +CaféCTRL is a software engineering project for CS2113. The user interacts with the application through CLI. It has around 6 kLoC, and it is done in a team of 5. + +## **Value Proposition** +CaféCRTL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers Café proprietors to streamline inventory and menu management. + +## **Target User** +Café proprietors who prefer typing on CLI than any other interaction method and are seeking for a software solution to optimize the management of their café's operations. + +----------------------------------------------------------------------------------------------- +## Summary of Contributions + +### Code Contributed +[Click here to see my code contribution :)](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=ShaniceTang&tabRepo=AY2324S1-CS2113-T17-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Enhancements Implemented +#### Features +1. **Delete Dish**
+ - Function: Allow user to delete a specific dish on the menu + - Command Format: `delete DISH_INDEX`
+ - Error Handling: This command is able to detect missing argument tag, missing argument, and invalid index. The error message will be shown to the user. +

+2. **Buy Ingredient**
+ - Function: Allow user to add ingredients to the Pantry. If ingredients have been previously added, its quantity will update accordingly
+ - Command Format: `buy_ingredient ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY[, ingredient/INGREDIENT2_NAME qty/INGREDIENT2_QTY, ...]`
+ - Error Handling: This command is able to detect the following user errors and outputs the appropriate error messages
+ - Missing argument tag / Missing arguments + - Quantity value less than 1 or greater than 1,000,000 + - Empty / Invalid Units +

+3. **View Total Stock**
+ - Function: Allows user to view the total stock currently in the Pantry + - Command Format: `view_stock` + +### Enhancement +1. **Storage**
+- Encoding of Menu object to a readable text file menu.txt +- Decoding of data in menu.txt to Menu object +

+2. **Parser**
+- Implemented parsing for the features listed above +- Improved precision of error handling by adding some checking methods: `isInvalidQty`, `isEmptyUnit`, `isValidUnit`, `checkForMismatchUnit` +

+3. **Pantry**
+- Implemented pantry class together with @NaychiMin which helps user to keep track of their current stock. This enhancement plays a huge role to several commands as it allows the user to smoothly interact with the pantry, hence it is important to ensure its completeness by ensuring it is able to seamlessly handle pantry-related operations. +- Allows user to add ingredients to pantry (through `buy_ingredient`) and updates the quantity of existing ingredients. +- Ensures user uses the same unit for existing ingredients as well as the menu. To allow this to be implemented, I helped modified the Ingredient class to accept units as a separate attribute and helped modify other classes/methods that instantiates an Ingredient object and create a method in Parser `checkForMismatchUnit` to reject mismatching units. +

+4. **Messages & ErrorMessages**
+- Provided meaningful messages that can be easily understood by the user + +### Contributions to UG +[UserGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html) +- Added documentation for the following features: Delete Dish from Menu, Buy Ingredients, View Total Stock, Add Order, Previous Day, Next Day +- Author tagged feature documentation to their respective contributors <3 + +### Contributions to DG +[DeveloperGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html) +- Designed various UML diagrams: Architecture Diagram, Ui Component Class Diagram, Data Component Class Diagram, Delete Sequence Diagram +- Added descriptions for Ui component, Delete Command, View Total Stock Command, and Buy Ingredient Command + +#### Team-based Task +1. Logging + - This was challenging as I encountered the issue of the log messages being printed out to system console which ruined the Ui. :( I had to do external research and found a solution to store log messages into a log file without displaying at runtime. :) +2. Maintaining issue tracker +3. Testing application and report bugs found +
![](../images_PPP/shanice/bug_reporting.png) + +### Review / Mentoring Contributions +1. Reviewed and merged some PRs [#300](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/300), [#322](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/322), [#283](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/283), ... +2. Clarified and updated the team before making major changes to ensure everyone is on the same page <3 + - Ensuring unused methods can be safely deleted with permission from the team member who wrote it + ![](../images_PPP/shanice/deleting_unused_method.png) + +### Contributions Beyond the Project Team +1. Reported bugs in other teams' application: [#105](https://github.com/AY2324S1-CS2113-W12-3/tp/issues/105), [#101](https://github.com/AY2324S1-CS2113-W12-3/tp/issues/101) +2. Reviewed other course ip / tp PRs: [#10](https://github.com/nus-cs2113-AY2324S1/ip/pull/10), [#63](https://github.com/nus-cs2113-AY2324S1/ip/pull/63), [#35](https://github.com/nus-cs2113-AY2324S1/tp/pull/35) \ No newline at end of file diff --git a/docs/team/ziyi105.md b/docs/team/ziyi105.md new file mode 100644 index 0000000000..11ea67fbe1 --- /dev/null +++ b/docs/team/ziyi105.md @@ -0,0 +1,103 @@ +# Zi Yi - Project Portfolio Page + +**Project: CafeCRTL** + +----------------------------------------------------------------------------------------------- +## **Overview** +CaféCTRL is a software engineering project for CS2113. The user interacts with the application through CLI. It has around 6 kLoC, and it is done in a team of 5. + +## **Value Proposition** +CaféCRTL aims to optimize managing of inventory and cash flow in a restaurant. Our CLI platform empowers Café proprietors to streamline inventory and menu management. + +----------------------------------------------------------------------------------------------- + +## **Summary of Contributions** +### Code Contribution + +[Follow here to see code written by me](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=ziyi105&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos&tabOpen=true&tabType=authorship&tabAuthor=ziyi105&tabRepo=AY2324S1-CS2113-T17-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Enhancements implemented + +#### Feature + +1. **Edit Price**
+ Function: Allow user to edit price of the dish on the menu + Command Format: `edit_price dish/DISH_INDEX price/NEW_PRICE`
+ Error Handling: This command is able to detect several wrong command formats and output specific error message. +
+ 2. **Help**
+ Function: Print out a list of commands and their usages to the user
+ Extra info: This feature requires constant updating whenever a new feature is added. +

+3. **Encoding & Decoding of Pantry Stock Storage File**
+ Error Handling: This command will still check for the validity of each argument, and skip the particular ingredient if any of the arguments is not valid. + +### Enhancements +1. **Storage**
+ [Relevant PR](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/143)
+ - Read and learned from the storage system in addressbook level 3 and adopted it in our project. + - Implemented `Encoder.java`, `Decoder.java` and `Storage.java` with skeleton methods for my teammates to implement. +
+2. **Parser**
+ Created `Parser.java` for other teammates to use. Implemented ParserUtil interface to reduce coupling of Parser.java and CafeCRTL.java. +
+3. **Messages & ErrorMessages**
+ Created classes to store all messages to the user as static constant. +
+4. **Parser Regex**
+ [Relevant PR](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/51)
+ - Learned `Pattern` and `Matcher` API by referring to addressbook and online documentations. + - Created regex `COMMAND_ARGUMENT_FORMAT`, `ADD_ARGUMENT_STRING`, `LIST_INGREDIENT_ARGUMENT_STRING`, `DELETE_ARGUMENT_STRING` and `EDIT_PRICE_ARGUMENT_STRING`. + - Created a skeleton method body of `prepare_add` method using pattern and matcher, and added some basic explanations on how to use them. + +### Contributions to UG +[UserGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/UserGuide.html) + +#### Individual Task +1. Edit price, Help + +#### Team-based Task +1. Table of Contents +2. Introduction +3. Known Issues +4. Glossary + +### Contributions to DG +[DeveloperGuide](https://ay2324s1-cs2113-t17-2.github.io/tp/DeveloperGuide.html) +#### Individual Task +1. Parser component +2. Edit Price +3. Help +4. Future Enhancements + +#### Team-based Task +1. Acknowledgement +2. Setting up, getting started +3. Product Scope +4. Non-Functional Requirements +5. Glossary +6. Manual testing guide + +### Other Contributions to Team-based Task +1. Maintaining issue tracker with Dexter by labeling issues +2. Approving and merging PRs +3. Checking coding standard for most of the classes ([PR #351](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/351)) +4. Reminding team members to use github effectively (although some of my reminders were ignored :") + - I encouraged my team members to report bugs using issue tracker on Github instead of using Telegram. (e.g., [#323](https://github.com/AY2324S1-CS2113-T17-2/tp/issues/323)) +5. Testing of features done my other team members + - [#382](https://github.com/AY2324S1-CS2113-T17-2/tp/issues/382), [#381](https://github.com/AY2324S1-CS2113-T17-2/tp/issues/381). [#372](https://github.com/AY2324S1-CS2113-T17-2/tp/issues/372), [#349](https://github.com/AY2324S1-CS2113-T17-2/tp/issues/349) + +### Review/Mentoring Contributions +1. Reviewed and approved 52 PRs in total. + Some examples of PR reviewed: [#167](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/167), [#106](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/106), [#57](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/57), + [#96](https://github.com/AY2324S1-CS2113-T17-2/tp/pull/96) +2. Shared my opinions in Telegram group.
+ + + +3. Read and compile important information on course website.
+ + +### Contributions beyond the Project Team +1. Posted 9 posts in the forum. Examples of forum posts: [#37](https://github.com/nus-cs2113-AY2324S1/forum/issues/37), [#19](https://github.com/nus-cs2113-AY2324S1/forum/issues/19), [#31](https://github.com/nus-cs2113-AY2324S1/forum/issues/31) +2. Reviews on PR from other teams: [T18-1](https://github.com/nus-cs2113-AY2324S1/tp/pull/19#discussion_r1379823357), [F11-3](https://github.com/nus-cs2113-AY2324S1/tp/pull/28) diff --git a/src/main/java/seedu/cafectrl/CafeCtrl.java b/src/main/java/seedu/cafectrl/CafeCtrl.java new file mode 100644 index 0000000000..470d8bcf3f --- /dev/null +++ b/src/main/java/seedu/cafectrl/CafeCtrl.java @@ -0,0 +1,98 @@ +package seedu.cafectrl; + +import seedu.cafectrl.command.Command; +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.parser.Parser; +import seedu.cafectrl.parser.ParserUtil; +import seedu.cafectrl.storage.Storage; +import seedu.cafectrl.ui.Ui; + +import java.io.IOException; +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.logging.FileHandler; +import java.util.logging.SimpleFormatter; + + +/** + * CafeCtrl application's entry point. + * Initializes the application and starts the interaction with the user. + */ +public class CafeCtrl { + + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private final Ui ui; + private final Storage storage; + private final Pantry pantry; + private final Menu menu; + private final Sales sales; + private final CurrentDate currentDate; + + private Command command; + + /** + * Private constructor for the CafeCtrl class, used for initializing the user interface and menu list. + */ + private CafeCtrl() { + initLogger(); + this.ui = new Ui(); + this.storage = new Storage(ui); + this.pantry = this.storage.loadPantryStock(); + this.menu = this.storage.loadMenu(); + this.sales = this.storage.loadOrderList(menu); + this.currentDate = new CurrentDate(sales); + + logger.info( "CafeCtrl initialised successfully"); + assert sales.getOrderLists().size() == currentDate.getCurrentDay() + 1; + } + + /** + * The main loop of the CafeCtrl application. + * + *

This method consistently receives user input, parses commands, and executes the respective command + * until the user enters a "bye" command, terminating the application.

+ */ + private void run() { + ui.showWelcome(); + ui.printLine(); + + do { + try { + String fullUserInput = ui.receiveUserInput(); + ParserUtil parserUtil = new Parser(); + command = parserUtil.parseCommand(menu, fullUserInput, ui, pantry, sales, currentDate); + command.execute(); + logger.info(command.getClass().getName() + " executed."); + } catch (Exception e) { + logger.log(Level.WARNING, "Error executing command: " + e.getMessage(), e); + ui.showToUser(e.getMessage()); + } finally { + ui.printLine(); + } + } while (!command.isExit()); + + storage.saveAll(menu, sales, pantry); + logger.info("CafeCtrl terminated."); + } + + private void initLogger() { + logger.setUseParentHandlers(false); + try { + FileHandler fileHandler = new FileHandler("cafeCtrl.log"); + SimpleFormatter formatter = new SimpleFormatter(); + + logger.addHandler(fileHandler); + fileHandler.setFormatter(formatter); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + CafeCtrl cafeCtrl = new CafeCtrl(); + cafeCtrl.run(); + } +} diff --git a/src/main/java/seedu/cafectrl/command/AddDishCommand.java b/src/main/java/seedu/cafectrl/command/AddDishCommand.java new file mode 100644 index 0000000000..da6f0fdb40 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/AddDishCommand.java @@ -0,0 +1,36 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; +//@@author DextheChik3n +/** + * Adds a menu item to the user + */ +public class AddDishCommand extends Command { + public static final String COMMAND_WORD = "add"; + public static final String MESSAGE_USAGE = "To add a new dish to the menu: \n" + + COMMAND_WORD + " name/DISH_NAME price/DISH_PRICE ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY" + + "[, ingredient/INGREDIENT2_NAME, qty/INGREDIENT2_QTY...]\n" + + "Example:" + + COMMAND_WORD + " name/chicken rice price/3.00 ingredient/rice qty/200g, ingredient/chicken qty/100g"; + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Menu menu; + protected Ui ui; + + private final Dish dish; + public AddDishCommand(Dish dish, Menu menu, Ui ui) { + this.dish = dish; + this.menu = menu; + this.ui = ui; + } + @Override + public void execute() { + logger.info("Executing AddDishCommand..."); + menu.addDish(dish); + ui.printAddDishMessage(dish); + } +} diff --git a/src/main/java/seedu/cafectrl/command/AddOrderCommand.java b/src/main/java/seedu/cafectrl/command/AddOrderCommand.java new file mode 100644 index 0000000000..918b8434ee --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/AddOrderCommand.java @@ -0,0 +1,58 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.Chef; + +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; +import java.text.DecimalFormat; + +public class AddOrderCommand extends Command { + public static final String COMMAND_WORD = "add_order"; + public static final String MESSAGE_USAGE = "To add a new order: \n" + + COMMAND_WORD + + " name/DISH_NAME qty/QUANTITY\n" + + "Example: " + COMMAND_WORD + + "name/chicken rice qty/2"; + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Pantry pantry; + protected OrderList orderList; + protected Menu menu; + private final Ui ui; + private final Order order; + private final DecimalFormat dollarValue = new DecimalFormat("0.00"); + + public AddOrderCommand(Order order, Ui ui, Pantry pantry, OrderList orderList, Menu menu) { + this.order = order; + this.ui = ui; + this.pantry = pantry; + this.orderList = orderList; + this.menu = menu; + } + @Override + public void execute() { + logger.info("Executing AddOrderCommand..."); + orderList.addOrder(order); + Chef chef = new Chef(order, pantry, ui); + chef.cookDish(); + if (order.getIsComplete()) { + orderList.addCost(order); + String totalCost = dollarValue.format(order.getTotalOrderCost()); + ui.showOrderStatus(totalCost); + pantry.calculateDishAvailability(menu, order); + } else { + //pass in dish only and not entire menu + Dish orderedDish = order.getOrderedDish(); + pantry.calculateMaxDishes(orderedDish, menu, order); + ui.showIncompleteOrder(); + } + + } + +} diff --git a/src/main/java/seedu/cafectrl/command/BuyIngredientCommand.java b/src/main/java/seedu/cafectrl/command/BuyIngredientCommand.java new file mode 100644 index 0000000000..100747defe --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/BuyIngredientCommand.java @@ -0,0 +1,99 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Ui; +import seedu.cafectrl.parser.Parser; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static seedu.cafectrl.ui.Ui.OFFSET_LIST_INDEX; + +//@@author ShaniceTang +/** + * The BuyIngredientCommand class represents a command to buy ingredients and add them to the pantry. + * It executes the command, adds the ingredients, and displays the results to the user. + */ +public class BuyIngredientCommand extends Command { + public static final String COMMAND_WORD = "buy_ingredient"; + public static final String MESSAGE_USAGE = "To buy ingredient:\n" + + COMMAND_WORD + " ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY" + + "[, ingredient/INGREDIENT2_NAME, qty/INGREDIENT2_QTY...]\n" + + "Example:" + + COMMAND_WORD + " ingredient/milk qty/200ml, ingredient/chicken qty/100g"; + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + + protected Ui ui; + protected Pantry pantry; + private ArrayList ingredients; + private ArrayList ingredientsToBePrinted = new ArrayList<>(); + private String ingredientString = ""; // Used to store the message about the bought ingredients + private int finalIndex = 0; + + /** + * Constructs a BuyIngredientCommand with the specified ingredients, user interface, and pantry. + * + * @param ingredients The list of ingredients to be bought and added to the pantry. + * @param ui The user interface to interact with the user. + * @param pantry The pantry to which the ingredients will be added. + */ + public BuyIngredientCommand(ArrayList ingredients, Ui ui, Pantry pantry) { + this.ingredients = ingredients; + this.ui = ui; + this.pantry = pantry; + } + + /** + * Executes the command to buy ingredients, adds them to the pantry, and displays the results to the user. + */ + @Override + public void execute() { + logger.info("Executing BuyIngredientCommand..."); + try { + addIngredient(); + ui.printBuyIngredientHeader(); + ui.showToUser(ingredientString.strip()); + } catch (Exception e) { + logger.log(Level.WARNING, "BuyIngredientCommand unsuccessful: " + e.getMessage(), e); + ui.showToUser(e.getMessage()); + } + } + + /** + * Adds the specified ingredients to the pantry. + * This method is called during command execution. + */ + private void addIngredient() { + for(int i = 0; i < ingredients.size(); i++) { + Ingredient ingredient = ingredients.get(i); + ingredient = pantry.addIngredientToStock(ingredient.getName().toLowerCase(), + ingredient.getQty(), + ingredient.getUnit()); + ingredients.set(i, ingredient); + } + + for (int i = ingredients.size() - OFFSET_LIST_INDEX; i >= finalIndex; i--) { + Ingredient ingredient = ingredients.get(i); + buildBuyIngredientMessage(ingredient); + } + } + + /** + * Builds a message about the bought ingredient and appends it to the result message. + * + * @param ingredient The Ingredient object to build the message for. + */ + private void buildBuyIngredientMessage(Ingredient ingredient) { + if (Parser.isRepeatedIngredientName(ingredient.getName(), ingredientsToBePrinted)) { + return; + } + ingredientsToBePrinted.add(ingredient); + ingredientString += "Ingredient: " + ingredient.getName() + + "\nTotal Qty: " + ingredient.getQty() + + ingredient.getUnit() + "\n\n"; + } +} + diff --git a/src/main/java/seedu/cafectrl/command/Command.java b/src/main/java/seedu/cafectrl/command/Command.java new file mode 100644 index 0000000000..b471bd351d --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/Command.java @@ -0,0 +1,28 @@ +package seedu.cafectrl.command; + +/** + * Represents an executable command. + */ +public class Command { + public int index; + + public void setIndex(int index) { + this.index = index; + } + + /** + * check whether this command is an exit command (user input "bye") + * + * default returns false, this method will be overridden in ExitCommand + */ + public boolean isExit() { + return false; + } + + /** + * Executes the command and returns the result. + */ + public void execute() { + throw new UnsupportedOperationException("This method is to be implemented by child classes"); + }; +} diff --git a/src/main/java/seedu/cafectrl/command/DeleteDishCommand.java b/src/main/java/seedu/cafectrl/command/DeleteDishCommand.java new file mode 100644 index 0000000000..47860cb9ff --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/DeleteDishCommand.java @@ -0,0 +1,47 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author ShaniceTang +/** + * Deletes a menu item identified using it's last displayed index from the menu. + */ +public class DeleteDishCommand extends Command { + public static final String COMMAND_WORD = "delete"; + public static final String MESSAGE_USAGE = "To delete a menu item:\n" + + COMMAND_WORD + + "Parameters: INDEX\n" + + "Example: " + COMMAND_WORD + " 1"; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Menu menu; + protected Ui ui; + + public DeleteDishCommand(int listIndex, Menu menu, Ui ui) { + this.index = listIndex; + this.menu = menu; + this.ui = ui; + } + + @Override + public void execute() { + logger.info("Executing DeleteDishCommand..."); + try { + int dishIndexToBeDeleted = index - Ui.OFFSET_LIST_INDEX; + Dish selectedDish = menu.getMenuItemsList().get(dishIndexToBeDeleted); + ui.printDeleteMessage(selectedDish); + menu.removeDish(dishIndexToBeDeleted); + } catch (IndexOutOfBoundsException e) { + logger.log(Level.WARNING, "DeleteDishCommand unsuccessful: " + e.getMessage(), e); + ui.showToUser(ErrorMessages.INVALID_DISH_INDEX); + throw new IndexOutOfBoundsException(); + } + } +} diff --git a/src/main/java/seedu/cafectrl/command/EditPriceCommand.java b/src/main/java/seedu/cafectrl/command/EditPriceCommand.java new file mode 100644 index 0000000000..5e31226c49 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/EditPriceCommand.java @@ -0,0 +1,50 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; + +//@@author ziyi105 +/** + * Edit the price of a dish of a certain index + */ +public class EditPriceCommand extends Command { + public static final String COMMAND_WORD = "edit_price"; + public static final String MESSAGE_USAGE = "To edit price of a menu item: \n" + + "edit_price dish/DISH_INDEX price/NEW_PRICE\n" + + "Example: edit_price dish/1 price/4.50"; + + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Menu menu; + protected Ui ui; + private final int menuID; + private final float newDishPrice; + + public EditPriceCommand(int menuID, float newDishPrice, Menu menu, Ui ui) { + this.menuID = menuID; + this.newDishPrice = newDishPrice; + this.menu = menu; + this.ui = ui; + } + + //@@author ziyi105 + /** + * Set new price of the dish and show edit price message + */ + public void execute() { + logger.info("Executing EditPriceCommand..."); + Dish dish = menu.getDishFromId(menuID - Ui.OFFSET_LIST_INDEX); + + // Checks for original price + if (dish.comparePrice(newDishPrice) == 0) { + ui.showToUser(ErrorMessages.EDIT_SAME_PRICE); + } else { + dish.setPrice(newDishPrice); + ui.showEditPriceMessage(dish.toString()); + } + } +} diff --git a/src/main/java/seedu/cafectrl/command/ExitCommand.java b/src/main/java/seedu/cafectrl/command/ExitCommand.java new file mode 100644 index 0000000000..e9f5f9967e --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/ExitCommand.java @@ -0,0 +1,34 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.ui.Ui; + +public class ExitCommand extends Command { + public static final String COMMAND_WORD = "bye"; + public static final String MESSAGE_USAGE = "To exit:\n" + + COMMAND_WORD; + + protected Ui ui; + protected Pantry pantry; + + public ExitCommand(Ui ui, Pantry pantry) { + this.ui = ui; + this.pantry = pantry; + } + + /** + * Overrides the isExit() method which returns false + * + * @return true + */ + @Override + public boolean isExit() { + return true; + } + + @Override + public void execute() { + ui.printLine(); + ui.showGoodbye(); + } +} diff --git a/src/main/java/seedu/cafectrl/command/HelpCommand.java b/src/main/java/seedu/cafectrl/command/HelpCommand.java new file mode 100644 index 0000000000..6f18624888 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/HelpCommand.java @@ -0,0 +1,34 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; + +//@@author ziyi105 +/** + * Show a list of commands for users to refer to + */ +public class HelpCommand extends Command { + public static final String COMMAND_WORD = "help"; + public static final String MESSAGE_USAGE = "To view all commands:\n" + COMMAND_WORD; + + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Ui ui; + + + public HelpCommand(Ui ui) { + this.ui = ui; + } + + /** + * Call ui to show list of commands + */ + @Override + public void execute() { + logger.info("Executing HelpCommand..."); + + ui.printLine(); + ui.showHelp(); + } +} diff --git a/src/main/java/seedu/cafectrl/command/IncorrectCommand.java b/src/main/java/seedu/cafectrl/command/IncorrectCommand.java new file mode 100644 index 0000000000..fd023d0011 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/IncorrectCommand.java @@ -0,0 +1,26 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; + +/** + * Represents an incorrect command. Upon execution, produces some feedback to the user. + */ +public class IncorrectCommand extends Command{ + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + public final String feedbackToUser; + protected Ui ui; + + public IncorrectCommand(String feedbackToUser, Ui ui) { + this.feedbackToUser = feedbackToUser; + this.ui = ui; + } + + @Override + public void execute() { + logger.warning("Executing IncorrectCommand: " + feedbackToUser); + ui.showToUser(feedbackToUser); + } +} diff --git a/src/main/java/seedu/cafectrl/command/ListIngredientCommand.java b/src/main/java/seedu/cafectrl/command/ListIngredientCommand.java new file mode 100644 index 0000000000..dc0bc5f2a8 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/ListIngredientCommand.java @@ -0,0 +1,44 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Lists all ingredients used in the selected dish to the user. + */ +public class ListIngredientCommand extends Command { + public static final String COMMAND_WORD = "list_ingredients"; + public static final String MESSAGE_USAGE = "To list out the ingredients needed " + + "along with the quantity for a specific dish:\n" + + "Parameters: dish/DISH_INDEX\n" + + "Example: " + COMMAND_WORD + " dish/1"; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Ui ui; + protected Menu menu; + + public ListIngredientCommand(int listIndex, Menu menu, Ui ui) { + this.index = listIndex; + this.menu = menu; + this.ui = ui; + } + + @Override + public void execute() { + logger.info("Executing ListIngredientCommand..."); + try { + Dish selectedDish = menu.getMenuItemsList().get(index - Ui.OFFSET_LIST_INDEX); + ui.showListIngredientsMessage(selectedDish); + } catch (IndexOutOfBoundsException e) { + logger.log(Level.WARNING, "ListIngredientCommand unsuccessful: " + e.getMessage(), e); + throw new IllegalArgumentException(ErrorMessages.UNLISTED_DISH); + } + } + +} diff --git a/src/main/java/seedu/cafectrl/command/ListMenuCommand.java b/src/main/java/seedu/cafectrl/command/ListMenuCommand.java new file mode 100644 index 0000000000..c203347213 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/ListMenuCommand.java @@ -0,0 +1,78 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Ui; + +import java.text.DecimalFormat; +import java.util.logging.Logger; + +//@@author Cazh1 +/** + * Lists all dishes in the menu to the user. + */ +public class ListMenuCommand extends Command { + public static final String COMMAND_WORD = "list_menu"; + public static final String MESSAGE_USAGE = "To view menu:\n" + + COMMAND_WORD; + private static final DecimalFormat dollarValue = new DecimalFormat("0.00"); + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private final Menu menu; + private final Ui ui; + + /** + * Constructor for the ListMenuCommand + * + * @param menu The menu object of the current session + * @param ui The ui object created that handles I/O with the user + */ + public ListMenuCommand(Menu menu, Ui ui) { + this.menu = menu; + this.ui = ui; + } + + /** + * Iterates through the menu arraylist, outputting the dish name and dish price. + * Calls printEmptyMenu() when (menu.getSize() == 0), printFullMenu() otherwise. + */ + @Override + public void execute() { + logger.info("Executing ListMenuCommand..."); + if (menu.getSize() == 0) { + printEmptyMenu(ui); + } else { + printFullMenu(menu, ui); + } + }; + + /** + * Shows empty menu message to user + * Called only when the menu is empty + * + * @param ui The ui object created that handles I/O with the user + */ + public void printEmptyMenu(Ui ui) { + ui.showEmptyMenu(); + } + + /** + * Prints the dishes in the menu + * Called only when the menu is not empty + * + * @param menu The menu object of the current session + * @param ui The ui object created that handles I/O with the user + */ + public void printFullMenu(Menu menu, Ui ui) { + ui.showMenuTop(); + for (int i = 0; i < menu.getSize(); i++) { + String indexNum = String.valueOf(i + 1); + Dish selectedDish = menu.getDishFromId(i); + String dishName = selectedDish.getName(); + String dishPrice = dollarValue.format(selectedDish.getPrice()); + ui.showMenuDish(indexNum, dishName, dishPrice); + } + ui.showMenuBottom(); + } +} diff --git a/src/main/java/seedu/cafectrl/command/ListSaleByDayCommand.java b/src/main/java/seedu/cafectrl/command/ListSaleByDayCommand.java new file mode 100644 index 0000000000..8d374cbfce --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/ListSaleByDayCommand.java @@ -0,0 +1,41 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; + +//@@author NaychiMin +public class ListSaleByDayCommand extends Command { + public static final String COMMAND_WORD = "list_sale"; + public static final String MESSAGE_USAGE = "To show sales for a chosen day:\n" + + COMMAND_WORD + " day/DAY_TO_DISPLAY\n" + + "Example: " + COMMAND_WORD + " day/1"; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private final int day; + private final Ui ui; + private final Sales sales; + + public ListSaleByDayCommand(int day, Ui ui, Sales sales) { + this.day = day; + this.ui = ui; + this.sales = sales; + } + + @Override + public void execute() { + logger.info("Executing ShowSalesByDayCommand..."); + try { + sales.printSaleByDay(ui, day); + } catch (Exception e) { + ui.showToUser(ErrorMessages.INVALID_SALE_DAY); + } + } + + public int getDay() { + return day; + } +} diff --git a/src/main/java/seedu/cafectrl/command/ListTotalSalesCommand.java b/src/main/java/seedu/cafectrl/command/ListTotalSalesCommand.java new file mode 100644 index 0000000000..9187616c3f --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/ListTotalSalesCommand.java @@ -0,0 +1,26 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; +public class ListTotalSalesCommand extends Command { + public static final String COMMAND_WORD = "list_total_sales"; + public static final String MESSAGE_USAGE = "To show sales for all days:\n" + COMMAND_WORD; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private Sales sales; + private Ui ui; + + public ListTotalSalesCommand(Sales sales, Ui ui) { + this.sales = sales; + this.ui = ui; + } + + @Override + public void execute() { + logger.info("Executing ShowSalesCommand..."); + sales.printSales(ui); + } +} diff --git a/src/main/java/seedu/cafectrl/command/NextDayCommand.java b/src/main/java/seedu/cafectrl/command/NextDayCommand.java new file mode 100644 index 0000000000..236fddc60f --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/NextDayCommand.java @@ -0,0 +1,50 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; + +//@@author Cazh1 +public class NextDayCommand extends Command { + public static final String COMMAND_WORD = "next_day"; + public static final String MESSAGE_USAGE = "To travel to next day:\n" + + COMMAND_WORD; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private final Ui ui; + private final Sales sales; + private final CurrentDate currentDate; + + public NextDayCommand(Ui ui, Sales sales, CurrentDate currentDate) { + this.ui = ui; + this.sales = sales; + this.currentDate = currentDate; + } + + /** + * Changes the current day to the next day. + * Checks if an orderList exist in Sales by comparing + * the intended Day vs the number of days accounted for in Sales + * + * If orderList does not exist, new OrderList is added to Sales + * The days accounted for in Sales is incremented + */ + @Override + public void execute() { + logger.info("Executing NextDayCommand..."); + ui.printLine(); + currentDate.nextDay(); + int nextDay = currentDate.getCurrentDay(); + if (nextDay > sales.getDaysAccounted()) { + OrderList newOrderList = new OrderList(); + sales.addOrderList(newOrderList); + sales.nextDay(); + } + ui.showNextDay(); + ui.showToUser("Today is Day " + (currentDate.getCurrentDay() + 1)); + } +} diff --git a/src/main/java/seedu/cafectrl/command/PreviousDayCommand.java b/src/main/java/seedu/cafectrl/command/PreviousDayCommand.java new file mode 100644 index 0000000000..d098fc1a85 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/PreviousDayCommand.java @@ -0,0 +1,31 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Logger; + +//@@author Cazh1 +public class PreviousDayCommand extends Command{ + public static final String COMMAND_WORD = "previous_day"; + public static final String MESSAGE_USAGE = "To go back to previous day:\n" + COMMAND_WORD; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Ui ui; + protected CurrentDate currentDate; + + public PreviousDayCommand(Ui ui, CurrentDate currentDate) { + this.ui = ui; + this.currentDate = currentDate; + } + + @Override + public void execute() { + logger.info("Executing PreviousDayCommand..."); + ui.printLine(); + currentDate.previousDay(); + ui.showPreviousDay(); + ui.showToUser("Today is Day " + (currentDate.getCurrentDay() + 1)); + } +} diff --git a/src/main/java/seedu/cafectrl/command/ViewTotalStockCommand.java b/src/main/java/seedu/cafectrl/command/ViewTotalStockCommand.java new file mode 100644 index 0000000000..7c87ed8790 --- /dev/null +++ b/src/main/java/seedu/cafectrl/command/ViewTotalStockCommand.java @@ -0,0 +1,43 @@ +package seedu.cafectrl.command; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Ui; +import seedu.cafectrl.ui.Messages; + +import java.util.ArrayList; +import java.util.logging.Logger; + +//@@author ShaniceTang +public class ViewTotalStockCommand extends Command { + public static final String COMMAND_WORD = "view_stock"; + public static final String MESSAGE_USAGE = "To view pantry stock:\n" + COMMAND_WORD; + + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected Ui ui; + protected Pantry pantry; + private ArrayList pantryStock; + + public ViewTotalStockCommand(Pantry pantry, Ui ui) { + this.pantry = pantry; + this.ui = ui; + } + + @Override + public void execute() { + logger.info("Executing ViewTotalStockCommand..."); + pantryStock = pantry.getPantryStock(); + + if (pantryStock.isEmpty()) { + ui.showToUser(Messages.EMPTY_STOCK); + return; + } + + ui.showIngredientTop(); + for (Ingredient ingredient : pantryStock) { + ui.showIngredientStock(ingredient.getName(), ingredient.getQty(), ingredient.getUnit()); + } + ui.showMenuBottom(); + } +} diff --git a/src/main/java/seedu/cafectrl/data/Chef.java b/src/main/java/seedu/cafectrl/data/Chef.java new file mode 100644 index 0000000000..c55d722973 --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/Chef.java @@ -0,0 +1,37 @@ +package seedu.cafectrl.data; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.ui.Ui; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Chef { + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private final Order order; + private final Pantry pantry; + private final Ui ui; + + public Chef(Order order, Pantry pantry, Ui ui) { + this.order = order; + this.pantry = pantry; + this.ui = ui; + } + + public void cookDish() { + logger.info("Checking if order can be made..."); + try { + if (!order.getIsComplete()) { + ui.showChefMessage(); + + boolean isComplete = pantry.isDishCooked(order.getIngredientList()); + order.setComplete(isComplete); + + logger.info("Dish cooked: " + isComplete); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Unsuccessful order: " + e.getMessage(), e); + ui.showToUser(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/cafectrl/data/CurrentDate.java b/src/main/java/seedu/cafectrl/data/CurrentDate.java new file mode 100644 index 0000000000..3a0f502233 --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/CurrentDate.java @@ -0,0 +1,49 @@ +package seedu.cafectrl.data; + +import java.util.ArrayList; + +public class CurrentDate { + private static final int MIN_ORDER_LIST_SIZE = 0; + private static final int DAY_BASE_NUMBER = 0; + private static final int DAY_OFFSET = 1; + private static final int ORDER_LIST_SIZE_OFFSET = 1; + private int currentDay; + + public CurrentDate() { + currentDay = DAY_BASE_NUMBER; + } + public CurrentDate(int day) { + currentDay = day - DAY_OFFSET; + } + public CurrentDate(Sales sales) { + setDate(sales); + } + + public void nextDay() { + currentDay += DAY_OFFSET; + } + + public void previousDay() { + currentDay -= DAY_OFFSET; + } + + public int getCurrentDay() { + return currentDay; + } + + /** + * Sets the current date to the latest date the user left off + * + * @param sales Used to access the number of order list created + */ + public void setDate(Sales sales) { + ArrayList orderLists = sales.getOrderLists(); + int orderListsSize = orderLists.size(); + + if (orderListsSize > MIN_ORDER_LIST_SIZE) { + currentDay = orderListsSize - ORDER_LIST_SIZE_OFFSET; + } else { + currentDay = DAY_BASE_NUMBER; + } + } +} diff --git a/src/main/java/seedu/cafectrl/data/Menu.java b/src/main/java/seedu/cafectrl/data/Menu.java new file mode 100644 index 0000000000..c6c9e90fd7 --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/Menu.java @@ -0,0 +1,91 @@ +package seedu.cafectrl.data; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class Menu { + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private ArrayList menuItems; + + public Menu() { + this.menuItems = new ArrayList<>(); + } + + public Menu(ArrayList menuItems) { + this.menuItems = menuItems; + } + + public ArrayList getMenuItemsList() { + return menuItems; + } + + public int getSize() { + return menuItems.size(); + } + + public Dish getDishFromId(int menuID) { + return menuItems.get(menuID); + } + + /** + * Checks if the ordered dish exist in the menu and returns the menu index if exist + * + * @param dishName Name of the ordered dish + * @return index of the dish in menu if exists, null if not found + */ + public Dish getDishFromName(String dishName) { + String formattedDishName = dishName.toLowerCase().trim(); + for (int i = 0; i < getSize(); i++) { + String menuDishName = getDishFromId(i).getName(); + String formattedMenuDishName = menuDishName.trim(); + if (formattedMenuDishName.equalsIgnoreCase(formattedDishName)) { + return getDishFromId(i); + } + } + return null; + } + + //@@author NaychiMin + /** + * Retrieves an ArrayList of Order objects representing aggregated orders for each menu item. + * Each Order object is initialized with a dish from the menu and a quantity of 0. + * Used in the print_sales function under Sales class. + * + * @return An ArrayList of Order objects representing aggregated orders for each menu item. + */ + public ArrayList getAggregatedOrders() { + logger.info("Getting aggregated orders..."); + ArrayList aggregatedOrders = new ArrayList<>(); + for (int i = 0; i < menuItems.size(); i++) { + Order order = new Order(menuItems.get(i), 0); + aggregatedOrders.add(order); + } + return aggregatedOrders; + } + + //@@author ziyi105 + /** + * Checks whether the dish index can be found in the menu + * + * @param dishIndex dish index to be checked + * @return true if it is valid, false otherwise + */ + public boolean isValidDishIndex(int dishIndex) { + logger.info("Checking if dish index " + dishIndex + " is valid..."); + + int offSetDishIndex = dishIndex - Ui.OFFSET_LIST_INDEX; + return offSetDishIndex >= 0 && offSetDishIndex < this.getSize(); + } + + //@@author DextheChik3n + public void removeDish(int menuID) { + menuItems.remove(menuID); + } + public void addDish(Dish dish) { + menuItems.add(dish); + } +} diff --git a/src/main/java/seedu/cafectrl/data/Order.java b/src/main/java/seedu/cafectrl/data/Order.java new file mode 100644 index 0000000000..157aae43d2 --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/Order.java @@ -0,0 +1,104 @@ +package seedu.cafectrl.data; + +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; + +import java.text.DecimalFormat; +import java.util.ArrayList; + +public class Order { + private static final DecimalFormat dollarValue = new DecimalFormat("0.00"); + private final Dish orderedDish; + private int dishQty; + private final ArrayList ingredientList; + private float totalOrderCost; + private boolean isComplete = false; + + public Order(Dish orderedDish, int dishQty) { + this.orderedDish = orderedDish; + this.dishQty = dishQty; + this.ingredientList = setIngredientList(); + this.totalOrderCost = calculateTotalOrderCost(); + } + + public Order(Dish orderedDish, int dishQty, float orderCost, boolean isComplete) { + this.orderedDish = orderedDish; + this.dishQty = dishQty; + this.ingredientList = setIngredientList(); + this.totalOrderCost = orderCost; + this.isComplete = isComplete; + } + + @Override + public String toString() { + return "Order: " + getDishName() + " Quantity: "+ dishQty + + "\nIngredientList: " + ingredientList + + "\nTotal Order Cost: $" + dollarValue.format(totalOrderCost); + } + + /** + * Calculates the total price of the order + * Multiplies cost per dish by number of dishes + * + * @return Total calculated cost + */ + public float calculateTotalOrderCost() { + float dishCost = orderedDish.getPrice(); + return dishCost * dishQty; + } + + /** + * Gets and prepares the ingredients used in the dish. + * Calculates the total ingredient used and stores in an Ingredient ArrayList + * + * @return Arraylist of Ingredients + */ + private ArrayList setIngredientList() { + ArrayList dishIngredient = new ArrayList<>(); + for (Ingredient ingredient : orderedDish.getIngredients()) { + String ingredientName = ingredient.getName(); + int ingredientQty = ingredient.getQty() * dishQty; + String ingredientUnit = ingredient.getUnit(); + + dishIngredient.add(new Ingredient(ingredientName, ingredientQty, ingredientUnit)); + } + return dishIngredient; + } + + public ArrayList getIngredientList() { + return ingredientList; + } + + public float getTotalOrderCost() { + return totalOrderCost; + } + + public void setComplete(boolean isComplete) { + this.isComplete = isComplete; + } + + public boolean getIsComplete() { + return isComplete; + } + + public String getDishName() { + return orderedDish.getName(); + } + + public int getQuantity() { + return dishQty; + } + + public void setQuantity(int quantity) { + this.dishQty = quantity; + } + + public void setTotalOrderCost(float cost) { + this.totalOrderCost = cost; + } + + public Dish getOrderedDish() { + return orderedDish; + } + +} diff --git a/src/main/java/seedu/cafectrl/data/OrderList.java b/src/main/java/seedu/cafectrl/data/OrderList.java new file mode 100644 index 0000000000..3b99f08c7d --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/OrderList.java @@ -0,0 +1,155 @@ +package seedu.cafectrl.data; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.ui.Ui; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * The OrderList class represents a list of orders for a specific day. + * It manages the collection of orders and calculates the total cost for the day. + */ +public class OrderList { + private static final DecimalFormat dollarValue = new DecimalFormat("0.00"); + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private ArrayList orderList; + private float totalOrderListCost; + + /** + * Constructs an empty OrderList with no orders and zero total order cost. + */ + public OrderList() { + this.orderList = new ArrayList<>(); + this.totalOrderListCost = 0; + } + + public ArrayList getOrderList() { + return orderList; + } + public int getSize() { + return orderList.size(); + } + + public void addOrder(Order order) { + orderList.add(order); + } + + public void addCost(Order order) { + float orderCost = order.getTotalOrderCost(); + totalOrderListCost += orderCost; + } + + //@@author NaychiMin + /** + * Prints the order list for a specific day, including dish names, quantities, and total cost prices. + * + */ + public void printOrderList(Ui ui) { + logger.info("Printing order list..."); + + if (orderList.isEmpty() || !hasCompletedOrders()) { + ui.showToUser("No sales for this day."); + return; + } + + ArrayList aggregatedOrders = new ArrayList<>(); + + for (Order order : orderList) { + aggregateOrder(order, aggregatedOrders); + } + + for (Order aggregatedOrder : aggregatedOrders) { + if (aggregatedOrder.getQuantity() == 0) { + continue; + } + + ui.showSalesAll(aggregatedOrder.getDishName(), + aggregatedOrder.getQuantity(), + dollarValue.format(aggregatedOrder.getTotalOrderCost())); + } + + ui.showSalesBottom(); + ui.showSalesCost("Total for day: ", "$" + dollarValue.format(calculateTotalCost(aggregatedOrders))); + ui.showSalesBottom(); + } + + /** + * Aggregates orders by updating quantities and total order costs for the same dish. + * + * @param order The Order object to be aggregated. + * @param aggregatedOrders The ArrayList of aggregated orders. + */ + private void aggregateOrder(Order order, ArrayList aggregatedOrders) { + logger.info("Aggregating order..."); + + if (order.getIsComplete()) { + int index = getIndexByDishName(aggregatedOrders, order.getDishName()); + //if dish is not found in aggregated orders, add the dish into it + if (index == -1) { + Order newOrderedDish = new Order(order.getOrderedDish(), order.getQuantity(), order.getTotalOrderCost(), + true); + aggregatedOrders.add(newOrderedDish); + } else { + //else add the quantities and totalCost accordingly + int currentTotalDishQty = aggregatedOrders.get(index).getQuantity(); + int orderedQty = order.getQuantity(); + float currentTotalDishCost = aggregatedOrders.get(index).getTotalOrderCost(); + float orderCost = order.getTotalOrderCost(); + aggregatedOrders.get(index).setQuantity(currentTotalDishQty + orderedQty); + aggregatedOrders.get(index).setTotalOrderCost(currentTotalDishCost + orderCost); + } + } + } + + /** + * Finds the index of an order in the aggregated orders list based on the dish name. + * + * @param aggregatedOrders The ArrayList of aggregated orders. + * @param dishName The dish name to search for. + * @return The index of the order with the specified dish name, or -1 if not found. + */ + private int getIndexByDishName(ArrayList aggregatedOrders, String dishName) { + for (int i = 0; i < aggregatedOrders.size(); i++) { + Order order = aggregatedOrders.get(i); + String orderDishName = order.getDishName().trim(); + dishName = dishName.trim(); + + if (orderDishName.equalsIgnoreCase(dishName)) { + return i; + } + } + return -1; + } + //@@author + + /** + * Calculates the total cost of all orders for a specific day. + * + * @param orders The ArrayList of orders. + * @return The total cost of all orders for the day. + */ + private float calculateTotalCost(ArrayList orders) { + logger.info("Calculating total cost..."); + float totalCost = 0; + + for (Order order : orders) { + totalCost += order.getTotalOrderCost(); + logger.info("Total cost: " + totalCost); + } + return totalCost; + } + public boolean isEmpty() { + return orderList.isEmpty(); + } + + public boolean hasCompletedOrders() { + for (Order order : orderList) { + if (order.getIsComplete()) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/cafectrl/data/Pantry.java b/src/main/java/seedu/cafectrl/data/Pantry.java new file mode 100644 index 0000000000..f32324d67c --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/Pantry.java @@ -0,0 +1,271 @@ +package seedu.cafectrl.data; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class Pantry { + public static final int DEFAULT_ORDER_QTY = 1; + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + + private final Ui ui; + private ArrayList pantryStock; + + //@@author NaychiMin + public Pantry(Ui ui, ArrayList pantryStock) { + this.ui = ui; + this.pantryStock = pantryStock; + } + + //@@author ShaniceTang + public Pantry(Ui ui) { + this.ui = ui; + this.pantryStock = new ArrayList<>(); + } + + /** + * Retrieves the current pantry stock from storage, which may include reading from a file (pantry.txt). + * + * @return An ArrayList of Ingredient objects representing the current pantry stock. + */ + public ArrayList getPantryStock() { + return pantryStock; + } + + /** + * Adds or updates an ingredient in the pantry stock based on its name and quantity. + * + * @param name The name of the ingredient to add or update. + * @param qty The quantity of the ingredient (e.g., "100g"). + * @param unit The unit of measurement for the quantity. + * @return The Ingredient object that was added or updated in the pantry stock. + */ + public Ingredient addIngredientToStock (String name, int qty, String unit) { + logger.info("Adding ingredients to stock..."); + pantryStock = getPantryStock(); //get latest pantry stock from pantry.txt + int ingredientIndex = getIndexOfIngredient(name); + + //if ingredient exists in pantry, add quantity of that ingredient + if (ingredientIndex != -1) { + logger.info(name + " exists in pantry, current quantity: "+ qty); + return addIngredientQuantity(qty, ingredientIndex, unit); + } + + //else, add new ingredient to pantry + Ingredient ingredient = new Ingredient(name, qty, unit); + pantryStock.add(ingredient); + return ingredient; + } + + /** + * Updates an ingredient's quantity in the pantry stock or adds a new ingredient if it doesn't exist. + * + * @param qty The quantity of the ingredient (e.g., "100g"). + * @param ingredientIndex The index of the ingredient in the pantry stock (-1 if not found). + * @return The Ingredient object that was added or updated in the pantry stock. + */ + private Ingredient addIngredientQuantity(int qty, int ingredientIndex, String unit) { + Ingredient ingredient = pantryStock.get(ingredientIndex); + + if (!unit.equalsIgnoreCase(ingredient.getUnit())) { + logger.warning("Unit does not match previous unit"); + throw new RuntimeException(ingredient.getName() + + ErrorMessages.UNIT_NOT_MATCHING + + ingredient.getUnit() + + ErrorMessages.IGNORE_REMAINING_INGREDIENTS); + } + qty += ingredient.getQty(); //adds new qty to current qty + ingredient.setQty(qty); + logger.info("New quantity: " + qty); + + return ingredient; + } + + /** + * Gets the index of an ingredient in the pantry stock based on its name (case-insensitive comparison). + * + * @param name The name of the ingredient to search for. + * @return The index of the ingredient in the pantry stock or -1 if not found. + */ + private int getIndexOfIngredient(String name) { + for (int i = 0; i < pantryStock.size(); i++) { + String ingredientName = pantryStock.get(i).getName().trim(); + + if (name.equalsIgnoreCase(ingredientName)) { + return i; + } + } + return -1; + } + + //@@author NaychiMin + /** + * Decreases the stock of ingredients based on the given dish order. + * + * @param dishIngredients Array of ingredients used to make the dish order. + */ + public boolean isDishCooked(ArrayList dishIngredients) { + logger.info("Checking if dish can be cooked"); + + //for each ingredient that is used in the dish, update the stock of ingredient left. + for (Ingredient dishIngredient : dishIngredients) { + Ingredient usedIngredientFromStock = getIngredient(dishIngredient); + + if (usedIngredientFromStock == null) { + return false; + } + + int stockQuantity = usedIngredientFromStock.getQty(); + int usedQuantity = dishIngredient.getQty(); + int finalQuantity = stockQuantity - usedQuantity; + + if (finalQuantity < 0) { + return false; + } + usedIngredientFromStock.setQty(finalQuantity); + } + return true; + } + + /** + * Retrieves the ingredient used in the ordered dish from pantryStock. + * + * @param dishIngredient The ingredient used in the ordered dish. + * @return The corresponding ingredient in pantryStock. + */ + private Ingredient getIngredient(Ingredient dishIngredient) { + return pantryStock.stream() + .filter(ingredient -> ingredient.getName().trim().equalsIgnoreCase(dishIngredient.getName().trim())) + .findFirst() + .orElse(null); + } + + //@@author NaychiMin + /** + * Checks the availability of dishes based on ingredient stock. + */ + + public void calculateDishAvailability(Menu menu, Order order) { + logger.info("Calculating dish availability..."); + int menuSize = menu.getSize(); + + for (int i = 0; i < menuSize; i++) { + Dish dish = menu.getDishFromId(i); + ui.showToUser("Dish: " + dish.getName()); + int numberOfDishes = calculateMaxDishes(dish, menu, order); + ui.showDishAvailability(numberOfDishes); + + if (i != menuSize - 1) { + ui.printLine(); + } + } + } + //@@author + /** + * Calculates the number of dishes that can be prepared with the available ingredients. + * + * @param dish The dish being ordered. + */ + public int calculateMaxDishes(Dish dish, Menu menu, Order order) { + logger.info("Calculating max number of dishes possible..."); + int maxNumofDish = Integer.MAX_VALUE; + boolean isRestockHeaderDisplayed = false; + ArrayList dishIngredients = retrieveIngredientsForDish(dish.getName(), menu); + + for (Ingredient dishIngredient : dishIngredients) { + int numOfDish = calculateMaxDishForEachIngredient(dishIngredient); + maxNumofDish = Math.min(numOfDish, maxNumofDish); + + if (!order.getIsComplete()) { + isRestockHeaderDisplayed = showRestockHeaderIfNeeded(isRestockHeaderDisplayed); + handleIncompleteDishCase(dishIngredient, order, numOfDish); + } else { + isRestockHeaderDisplayed = (numOfDish == 0) ? showRestockHeaderIfNeeded(isRestockHeaderDisplayed) + : isRestockHeaderDisplayed; + handleZeroDishCase(dishIngredient, numOfDish); + } + } + + return maxNumofDish; + } + + private boolean showRestockHeaderIfNeeded(boolean isRestockHeaderDisplayed) { + if (!isRestockHeaderDisplayed) { + ui.showToUser(Messages.RESTOCK_CORNER, Messages.RESTOCK_TITLE, Messages.RESTOCK_CORNER); + isRestockHeaderDisplayed = true; + } + return isRestockHeaderDisplayed; + } + + private void handleIncompleteDishCase(Ingredient dishIngredient, Order order, int numOfDish) { + int orderQuantity = order.getQuantity(); + + if (numOfDish < orderQuantity) { + handleRestock(dishIngredient, orderQuantity); + } + } + private void handleZeroDishCase(Ingredient dishIngredient, int numOfDish) { + if (numOfDish == 0) { + handleRestock(dishIngredient, DEFAULT_ORDER_QTY); + } + } + + /** + * Calculates the number of dishes that can be prepared with the provided ingredients. + * + * @param dishIngredient The ingredient used in the ordered dish. + * @return The number of dishes that can be prepared. + */ + private int calculateMaxDishForEachIngredient(Ingredient dishIngredient) { + logger.info("Calculating max dish for each ingredient..."); + Ingredient usedIngredientFromStock = getIngredient(dishIngredient); + + if (usedIngredientFromStock == null) { + return 0; + } + + int currentQuantity = usedIngredientFromStock.getQty(); + int usedQuantity = dishIngredient.getQty(); + return currentQuantity / usedQuantity; + } + + /** + * Handles the case when restocking is required for a specific ingredient. + * + * @param dishIngredient The ingredient for which restocking is needed. + */ + private void handleRestock(Ingredient dishIngredient, int dishQty) { + String dishIngredientName = dishIngredient.getName(); + Ingredient stockIngredient = getIngredient(dishIngredient); + + int currentQuantity = (stockIngredient == null) ? 0 : stockIngredient.getQty(); + String unit = dishIngredient.getUnit(); + int neededQuantity = dishIngredient.getQty() * dishQty; + ui.showNeededRestock(dishIngredientName, currentQuantity, unit, neededQuantity); + } + + /** + * Retrieves the ingredients for a specific ordered dish. + * + * @param orderedDish The name of the ordered dish. + * @return The list of ingredients for the ordered dish. + */ + public ArrayList retrieveIngredientsForDish(String orderedDish, Menu menu) { + ArrayList dishIngredients = new ArrayList<>(); + + //retrieving the ingredients for orderedDish + for (Dish dish : menu.getMenuItemsList()) { + if (dish.getName().equals(orderedDish)) { + dishIngredients.addAll(dish.getIngredients()); + break; + } + } + return dishIngredients; + } +} diff --git a/src/main/java/seedu/cafectrl/data/Sales.java b/src/main/java/seedu/cafectrl/data/Sales.java new file mode 100644 index 0000000000..4c8e310ffd --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/Sales.java @@ -0,0 +1,116 @@ +package seedu.cafectrl.data; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +/** + * The Sales class represents sales data over a period of time, maintaining a collection of order lists. + */ +public class Sales { + public static final int DAY_DISPLAY_OFFSET = 1; + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private static ArrayList orderLists; + private int daysAccounted; + + public Sales() { + this.orderLists = new ArrayList<>(); + orderLists.add(new OrderList()); + this.daysAccounted = 0; + } + + public Sales(ArrayList orderLists) { + this.orderLists = orderLists; + this.daysAccounted = orderLists.size() - DAY_DISPLAY_OFFSET; + } + + public Sales(OrderList orderList) { + this.orderLists = new ArrayList<>(); + orderLists.add(orderList); + this.daysAccounted = orderLists.size() - DAY_DISPLAY_OFFSET; + } + + public void addOrderList(OrderList orderList) { + orderLists.add(orderList); + } + + public void nextDay() { + this.daysAccounted += DAY_DISPLAY_OFFSET; + } + + public int getDaysAccounted() { + return daysAccounted; + } + + public ArrayList getOrderLists() { + return orderLists; + } + + public OrderList getOrderList(int index) { + return orderLists.get(index); + } + + //@@author NaychiMin + /** + * Prints all sales data, organized by day, including dish names, quantities, and total cost prices. + * + * @param ui The Ui object for user interface interactions. + */ + public void printSales(Ui ui) { + if(isOrderListsEmpty()) { + logger.info("Printing empty sales..."); + ui.showToUser("No sales made."); + return; + } + + for (int day = 0; day < orderLists.size(); day++) { + logger.info("Printing sales for day " + day + "..."); + OrderList orderList = orderLists.get(day); + + if (orderList.isEmpty() || !orderList.hasCompletedOrders()) { + ui.showToUser("No sales for day " + (day + DAY_DISPLAY_OFFSET) + "."); + continue; + } + + ui.showSalesTop(day + DAY_DISPLAY_OFFSET); + orderList.printOrderList(ui); + } + } + + /** + * Prints sales data for a specific day, including dish names, quantities, and total cost prices. + * + * @param ui The Ui object for user interface interactions. + * @param day The day for which sales data is to be printed. + */ + public void printSaleByDay(Ui ui, int day) { + logger.info("Printing sales by day..."); + int orderListIndex = day - DAY_DISPLAY_OFFSET; + try { + OrderList orderList = orderLists.get(orderListIndex); + if (orderList.isEmpty() || !orderList.hasCompletedOrders()) { + ui.showToUser("No sales for this day."); + return; + } + + ui.showSalesTop(day); + orderList.printOrderList(ui); + } catch (Exception e) { + logger.log(Level.WARNING, "Unable to print sales for day " + day + "\n" + e.getMessage(), e); + ui.showToUser(ErrorMessages.INVALID_SALE_DAY); + } + } + + public boolean isOrderListsEmpty() { + for (OrderList orderList : orderLists) { + if (!orderList.isEmpty()) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/seedu/cafectrl/data/dish/Dish.java b/src/main/java/seedu/cafectrl/data/dish/Dish.java new file mode 100644 index 0000000000..b774fee6f7 --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/dish/Dish.java @@ -0,0 +1,61 @@ +package seedu.cafectrl.data.dish; + +import java.util.ArrayList; +import java.text.DecimalFormat; + +public class Dish { + private final DecimalFormat dollarValue = new DecimalFormat("0.00"); + + private final String name; + private final ArrayList ingredients; + private float price; + + public Dish(String name, ArrayList ingredients, float price) { + this.name = name; + this.ingredients = ingredients; + this.price = Math.abs(price); + } + public Dish(String name, float price) { + this.name = name; + this.ingredients = new ArrayList<>(); + this.price = Math.abs(price); + } + + public String getName() { + return name; + } + + public ArrayList getIngredients() { + return ingredients; + } + + public float getPrice() { + return price; + } + + public String getPriceString() { + return this.dollarValue.format(this.price); + } + + public void setPrice(float newPrice) { + this.price = Math.abs(newPrice); + } + + //@@author ziyi105 + @Override + public String toString() { + return this.name + " $" + this.dollarValue.format(this.price); + } + + /** + * Compare the original price and new price + * @param otherPrice price value to be compared with + * @return 0 if both price values are the same, -1 if the this.price is lower, +1 otherwise + */ + public int comparePrice(float otherPrice) { + String formattedPrice = this.dollarValue.format(price); + String formattedNewPrice = this.dollarValue.format(otherPrice); + + return formattedPrice.compareTo(formattedNewPrice); + } +} diff --git a/src/main/java/seedu/cafectrl/data/dish/Ingredient.java b/src/main/java/seedu/cafectrl/data/dish/Ingredient.java new file mode 100644 index 0000000000..c6bbfddef1 --- /dev/null +++ b/src/main/java/seedu/cafectrl/data/dish/Ingredient.java @@ -0,0 +1,55 @@ +package seedu.cafectrl.data.dish; + +public class Ingredient { + private final String name; + private final String unit; + private int qty; + + public Ingredient(String name) { + this.name = name; + this.unit = null; + this.qty = 0; + } + + public Ingredient(String name, int qty, String unit) { + this.name = name; + this.unit = unit; + this.qty = qty; + } + + public String getName() { + return name; + } + + public int getQty() { + return qty; + } + + public String getUnit() { + return unit; + } + + public void setQty(int qty) { + this.qty = qty; + } + + @Override + public String toString() { + return name + " - " + qty + unit; + } + + /** + * Compare the name of this ingredient to the other ingredient + * + * @param obj the other ingredient to be compared with + * @return true if they have the same name, false otherwise + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Ingredient) { + String otherName = ((Ingredient) obj).name; + return this.name.equals(otherName); + } + return false; + } +} diff --git a/src/main/java/seedu/cafectrl/parser/Parser.java b/src/main/java/seedu/cafectrl/parser/Parser.java new file mode 100644 index 0000000000..ab65d4db4c --- /dev/null +++ b/src/main/java/seedu/cafectrl/parser/Parser.java @@ -0,0 +1,907 @@ +package seedu.cafectrl.parser; + + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.command.AddDishCommand; +import seedu.cafectrl.command.AddOrderCommand; +import seedu.cafectrl.command.BuyIngredientCommand; +import seedu.cafectrl.command.Command; +import seedu.cafectrl.command.DeleteDishCommand; +import seedu.cafectrl.command.EditPriceCommand; +import seedu.cafectrl.command.ExitCommand; +import seedu.cafectrl.command.HelpCommand; +import seedu.cafectrl.command.IncorrectCommand; +import seedu.cafectrl.command.ListIngredientCommand; +import seedu.cafectrl.command.ListMenuCommand; +import seedu.cafectrl.command.NextDayCommand; +import seedu.cafectrl.command.PreviousDayCommand; +import seedu.cafectrl.command.ListTotalSalesCommand; +import seedu.cafectrl.command.ListSaleByDayCommand; +import seedu.cafectrl.command.ViewTotalStockCommand; + +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.parser.exception.ParserException; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Parse everything received from the users on terminal + * into a format that can be interpreted by other core classes + */ +public class Parser implements ParserUtil { + private static final String COMMAND_ARGUMENT_REGEX = "(?\\S+)\\s*(?.*)"; + + //@@author DextheChik3n + /** Add Dish Command Handler Patterns*/ + private static final String ADD_ARGUMENT_FORMAT_REGEX = "name/(?.*) " + + "price/(?\\s*\\S*)\\s+(?ingredient/.*)"; + private static final String DISH_NAME_MATCHER_GROUP_LABEL = "dishName"; + private static final String PRICE_MATCHER_GROUP_LABEL = "dishPrice"; + private static final String INGREDIENTS_MATCHER_GROUP_LABEL = "ingredients"; + private static final String INGREDIENT_ARGUMENT_FORMAT_REGEX = "\\s*ingredient/(?.*) " + + "qty/\\s*(?.*)\\s*"; + private static final String INGREDIENT_NAME_REGEX_GROUP_LABEL = "ingredientName"; + private static final String INGREDIENT_QTY_REGEX_GROUP_LABEL = "ingredientQty"; + private static final String INGREDIENT_QTY_FORMAT_REGEX = "^\\s*(?[+-]*[0-9]*)\\s*(?[a-zA-z]*)\\s*$"; + private static final String INGREDIENT_QTY_VALUE_REGEX_GROUP_LABEL = "value"; + private static final String INGREDIENT_QTY_UNIT_REGEX_GROUP_LABEL = "unit"; + private static final String ADD_DISH_NAME_ARGUMENT = "name/"; + private static final String ADD_DISH_PRICE_ARGUMENT = "price/"; + private static final String INGREDIENT_ARGUMENT = "ingredient/"; + private static final String QTY_ARGUMENT = "qty/"; + private static final String INGREDIENT_DIVIDER_REGEX = ","; + + /** Add Order Command Handler Patterns*/ + private static final int DISH_NAME_MATCHER_GROUP_NUM = 1; + private static final int ORDER_QTY_MATCHER_GROUP_NUM = 2; + private static final String ADD_ORDER_ARGUMENT_STRING = "name/(.*) qty/(.*)"; + + /** The rest of Command Handler Patterns*/ + + private static final String LIST_INGREDIENTS_ARGUMENT_STRING = "dish/(.+)"; + private static final String DELETE_ARGUMENT_STRING = "(.+)"; + private static final String EDIT_PRICE_ARGUMENT_STRING = "dish/(.*)\\sprice/(.*)"; + private static final String BUY_INGREDIENT_ARGUMENT_STRING = "(ingredient/[A-Za-z0-9\\s]+ qty/.+" + + "(?:, ingredient/[A-Za-z0-9\\s]+ qty/.+)*)"; + private static final String SHOW_SALE_BY_DAY_ARGUMENT_STRING = "day/(.+)"; + private static final int MIN_QTY = 1; + private static final int MAX_QTY = 1000000; + private static final String GRAMS_UNIT = "g"; + private static final String ML_UNIT = "ml"; + private static final String PRICE_INPUT_REGEX = "^-?[0-9]\\d*(\\.\\d{0,2})?$"; + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + + //@@author ziyi105 + /** + * Parse userInput and group it under commandWord and arguments + * use commandWord to find the matching command and prepare the command + * + * @param menu The arraylist object created that stores current tasks + * @param userInput The full user input String + * @param ui The ui object created that handles I/O with the user + * @param pantry The arraylist object created that stores current ingredients in stock + * @return command requested by the user + */ + public Command parseCommand(Menu menu, String userInput, Ui ui, + Pantry pantry, Sales sales, CurrentDate currentDate) { + logger.info("Received user input: " + userInput); + + Pattern userInputPattern = Pattern.compile(COMMAND_ARGUMENT_REGEX); + final Matcher matcher = userInputPattern.matcher(userInput.trim()); + + if (!matcher.matches()) { + logger.warning("Unmatched regex!"); + return new IncorrectCommand(ErrorMessages.UNKNOWN_COMMAND_MESSAGE, ui); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + + case AddDishCommand.COMMAND_WORD: + return prepareAdd(arguments, menu, ui); + + case DeleteDishCommand.COMMAND_WORD: + return prepareDelete(menu, arguments, ui); + + case ListIngredientCommand.COMMAND_WORD: + return prepareListIngredient(menu, arguments, ui); + + case ListMenuCommand.COMMAND_WORD: + return prepareListMenu(menu, ui); + + case EditPriceCommand.COMMAND_WORD: + return prepareEditPriceCommand(menu, arguments, ui); + + case ViewTotalStockCommand.COMMAND_WORD: + return prepareViewTotalStock(ui, pantry); + + case BuyIngredientCommand.COMMAND_WORD: + return prepareBuyIngredient(arguments, ui, pantry, menu); + + case HelpCommand.COMMAND_WORD: + return prepareHelpCommand(ui); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(ui, pantry); + + case AddOrderCommand.COMMAND_WORD: + return prepareOrder(menu, arguments, ui, pantry, sales, currentDate); + + case NextDayCommand.COMMAND_WORD: + return prepareNextDay(ui, sales, currentDate); + + case PreviousDayCommand.COMMAND_WORD: + return preparePreviousDay(ui, currentDate); + + case ListTotalSalesCommand.COMMAND_WORD: + return prepareShowSales(sales, ui, arguments); + + case ListSaleByDayCommand.COMMAND_WORD: + return prepareShowSalesByDay(arguments, ui, sales); + + default: + logger.warning(ErrorMessages.UNKNOWN_COMMAND_MESSAGE); + return new IncorrectCommand(ErrorMessages.UNKNOWN_COMMAND_MESSAGE, ui); + } + } + + //@@author Cazh1 + /** + * Prepares the ListMenuCommand + * + * @param menu menu of the current session + * @param ui ui of the current session + * @return new ListMenuCommand + */ + private static Command prepareListMenu(Menu menu, Ui ui) { + return new ListMenuCommand(menu, ui); + } + + //@@author ziyi105 + /** + * Parse argument in the context of edit price command + * + * @param menu menu of the current session + * @param arguments string that matches group arguments + * @return new EditDishCommand + */ + private static Command prepareEditPriceCommand(Menu menu, String arguments, Ui ui) { + Pattern editDishArgumentsPattern = Pattern.compile(EDIT_PRICE_ARGUMENT_STRING); + Matcher matcher = editDishArgumentsPattern.matcher(arguments); + + // Checks whether the overall pattern of edit price arguments is correct + if (!matcher.find()) { + logger.log(Level.WARNING, "Unmatched regex!"); + return new IncorrectCommand(ErrorMessages.MISSING_ARGUMENT_FOR_EDIT_PRICE, ui); + } + + int dishIndexGroup = 1; + int newPriceGroup = 2; + int dishIndex; + float newDishPrice; + + try { + String dishIndexText = matcher.group(dishIndexGroup).trim(); + + // Check whether the index is empty + if (dishIndexText.isEmpty()) { + logger.warning("Empty dish index!"); + return new IncorrectCommand(ErrorMessages.MISSING_DISH_IN_EDIT_PRICE, ui); + } + + dishIndex = Integer.parseInt(dishIndexText); + + // Check whether the dish index is valid + if (!menu.isValidDishIndex(dishIndex)) { + logger.warning("Invalid dish index!"); + return new IncorrectCommand(ErrorMessages.INVALID_DISH_INDEX, ui); + } + } catch (NumberFormatException e) { + logger.log(Level.WARNING, "Invalid dish index type!", e); + return new IncorrectCommand(ErrorMessages.WRONG_DISH_INDEX_TYPE_FOR_EDIT_PRICE, ui); + } + + try { + newDishPrice = parsePriceToFloat(matcher.group(newPriceGroup).trim()); + } catch (ParserException e) { + logger.log(Level.WARNING, "Invalid price!", e); + return new IncorrectCommand(e.getMessage(), ui); + } + + return new EditPriceCommand(dishIndex, newDishPrice, menu, ui); + } + + //@@author DextheChik3n + /** + * Parses the user input text into ingredients to form a Dish + * that is added to the Menu + * + * @param arguments string that matches group arguments + * @param menu Menu of the current session + * @param ui Ui of the current session + * @return new AddDishCommand + */ + private static Command prepareAdd(String arguments, Menu menu, Ui ui) { + try { + Matcher matcher = detectErrorInPreAddParse(arguments); + // Checks whether the overall pattern of add arguments is correct + if (!matcher.matches()) { + logger.log(Level.WARNING, "Unmatched regex!"); + return new IncorrectCommand(ErrorMessages.INVALID_ADD_DISH_FORMAT + + AddDishCommand.MESSAGE_USAGE, ui); + } + + // To retrieve specific arguments from arguments + //the dishName needs .trim() because the regex accepts whitespaces in the "name/" argument + String dishName = matcher.group(DISH_NAME_MATCHER_GROUP_LABEL).trim().toLowerCase(); + float dishPrice = parsePriceToFloat(matcher.group(PRICE_MATCHER_GROUP_LABEL)); + String ingredientsListString = matcher.group(INGREDIENTS_MATCHER_GROUP_LABEL); + + detectErrorPostDishNameParse(dishName, menu, true); + + ArrayList ingredients = parseIngredients(ingredientsListString, true, menu); + Dish dish = new Dish(dishName, ingredients, dishPrice); + + return new AddDishCommand(dish, menu, ui); + } catch (NullPointerException e) { + logger.log(Level.WARNING, e.getMessage(), e); + return new IncorrectCommand(ErrorMessages.NULL_NAME_DETECTED_MESSAGE, ui); + } catch (NumberFormatException e) { + logger.log(Level.WARNING, e.getMessage(), e); + return new IncorrectCommand(ErrorMessages.INVALID_INGREDIENT_QTY, ui); + } catch (Exception e) { + logger.log(Level.WARNING, e.getMessage(), e); + return new IncorrectCommand(e.getMessage(), ui); + } + } + + private static Matcher detectErrorInPreAddParse(String arguments) throws ParserException { + if (isRepeatedArgument(arguments, ADD_DISH_NAME_ARGUMENT)) { + logger.log(Level.WARNING, "Repeated dish/ argument!"); + throw new ParserException(ErrorMessages.REPEATED_NAME_ARGUMENT); + } else if (isRepeatedArgument(arguments, ADD_DISH_PRICE_ARGUMENT)) { + logger.log(Level.WARNING, "Repeated price/ argument!"); + throw new ParserException(ErrorMessages.REPEATED_PRICE_ARGUMENT); + } + + // Checks whether the overall pattern of add arguments is correct + Pattern addArgumentPattern = Pattern.compile(ADD_ARGUMENT_FORMAT_REGEX); + Matcher matcher = addArgumentPattern.matcher(arguments); + + if (!matcher.matches()) { + logger.log(Level.WARNING, "Unmatched regex!"); + throw new ParserException(ErrorMessages.INVALID_ADD_DISH_FORMAT + AddDishCommand.MESSAGE_USAGE); + } + + return matcher; + } + + private static void detectErrorPostDishNameParse(String dishName, Menu menu, boolean isCheckRepeatedDishName) + throws ParserException { + if (dishName.isEmpty()) { + logger.warning("Dish name empty!"); + throw new ParserException(ErrorMessages.MISSING_DISH_NAME); + } else if (isNameLengthInvalid(dishName)) { + logger.warning("Invalid name length!"); + throw new ParserException(ErrorMessages.INVALID_DISH_NAME_LENGTH_MESSAGE); + } else if (isCheckRepeatedDishName && isRepeatedDishName(dishName, menu)) { + logger.warning("Repeated dish!"); + throw new ParserException(ErrorMessages.REPEATED_DISH_MESSAGE); + } else if (containsSpecialChar(dishName)) { + logger.warning("Special character in dish name!"); + throw new ParserException(ErrorMessages.NAME_CANNOT_CONTAIN_SPECIAL_CHAR); + } + } + + /** + * Parses the user's input text ingredients. + * + * @param ingredientsListString user's input string of ingredients, + * multiple ingredients seperated by ',' is allowed + * @param menu + * @return list of ingredients that consists of the dish + * @throws ParserException if the input string does not match the constraints + * @throws NumberFormatException if the string value of the ingredient qty does not contain a parsable integer. + */ + private static ArrayList parseIngredients( + String ingredientsListString, boolean isExcludeRepeatedIngredients, Menu menu) + throws ParserException, NumberFormatException { + logger.info("Parsing ingredients..."); + String[] inputIngredientList = {ingredientsListString}; + ArrayList ingredients = new ArrayList<>(); + + //check if there is more than 1 ingredient + if (ingredientsListString.contains(INGREDIENT_DIVIDER_REGEX)) { + //split the whole string of ingredients into separate individual ingredients + inputIngredientList = ingredientsListString.split(INGREDIENT_DIVIDER_REGEX); + } + + //Parsing each ingredient + for (String inputIngredient: inputIngredientList) { + parseIngredient(isExcludeRepeatedIngredients, menu, inputIngredient, ingredients); + } + + return ingredients; + } + + private static void parseIngredient( + boolean isExcludeRepeatedIngredients, Menu menu, + String inputIngredient, ArrayList ingredients) + throws ParserException { + Matcher ingredientMatcher = detectErrorPreIngredientParse(inputIngredient); + + String ingredientName = ingredientMatcher.group(INGREDIENT_NAME_REGEX_GROUP_LABEL).trim().toLowerCase(); + + //ingredientQtyString contains the input text after the "qty/" argument + String ingredientQtyString = ingredientMatcher.group(INGREDIENT_QTY_REGEX_GROUP_LABEL).trim(); + + //check the formatting of text after ingredient qty argument (qty/) + final Pattern ingredientQtyFormatPattern = Pattern.compile(INGREDIENT_QTY_FORMAT_REGEX); + Matcher ingredientQtyMatcher = ingredientQtyFormatPattern.matcher(ingredientQtyString); + + if (!ingredientQtyMatcher.matches()) { + throw new ParserException(ErrorMessages.INVALID_INGREDIENT_QTY_FORMAT); + } + + String ingredientUnit = ingredientQtyMatcher.group(INGREDIENT_QTY_UNIT_REGEX_GROUP_LABEL); + int ingredientQty = Integer.parseInt(ingredientQtyMatcher + .group(INGREDIENT_QTY_VALUE_REGEX_GROUP_LABEL)); + + detectErrorPostIngredientParse(isExcludeRepeatedIngredients, + ingredientName, ingredientQty, ingredientUnit, ingredients); + + Ingredient ingredient = new Ingredient(ingredientName, ingredientQty, ingredientUnit); + + checkForMismatchUnit(menu, ingredient); + + ingredients.add(ingredient); + } + + private static Matcher detectErrorPreIngredientParse(String inputIngredient) + throws ParserException { + if (isRepeatedArgument(inputIngredient, INGREDIENT_ARGUMENT)) { + logger.log(Level.WARNING, "Repeated ingredient/ argument!"); + throw new ParserException(ErrorMessages.REPEATED_INGREDIENT_ARGUMENT); + } else if (isRepeatedArgument(inputIngredient, QTY_ARGUMENT)) { + logger.log(Level.WARNING, "Repeated qty/ argument!"); + throw new ParserException(ErrorMessages.REPEATED_QTY_ARGUMENT); + } + + final Pattern ingredientPattern = Pattern.compile(INGREDIENT_ARGUMENT_FORMAT_REGEX); + Matcher ingredientMatcher = ingredientPattern.matcher(inputIngredient); + + if (!ingredientMatcher.matches()) { + logger.log(Level.WARNING, "Mismatched ingredient arguments!"); + throw new ParserException(ErrorMessages.INVALID_INGREDIENT_ARGUMENTS); + } + + return ingredientMatcher; + } + + private static void detectErrorPostIngredientParse( + boolean isExcludeRepeatedIngredients, String ingredientName, int ingredientQty, + String ingredientUnit, ArrayList ingredients) throws ParserException { + + //error case + if (ingredientName.isEmpty()) { + logger.log(Level.WARNING, "Missing ingredient name!"); + throw new ParserException(ErrorMessages.MISSING_INGREDIENT_NAME); + } else if (isNameLengthInvalid(ingredientName)) { + logger.log(Level.WARNING, "Exceed max ingredient name length!"); + throw new ParserException(ErrorMessages.INVALID_INGREDIENT_NAME_LENGTH_MESSAGE); + } else if (isInvalidQty(ingredientQty)) { + logger.log(Level.WARNING, "Exceed ingredient qty range!"); + throw new ParserException(ErrorMessages.INVALID_INGREDIENT_QTY); + } else if (isEmptyUnit(ingredientUnit)) { + logger.log(Level.WARNING, "Missing ingredient qty unit!"); + throw new ParserException(ErrorMessages.EMPTY_UNIT_MESSAGE); + } else if (!isValidUnit(ingredientUnit)) { + logger.log(Level.WARNING, "Invalid ingredient qty unit!"); + throw new ParserException(ErrorMessages.INVALID_UNIT_MESSAGE); + } else if (containsSpecialChar(ingredientName)) { + logger.log(Level.WARNING, "Special character in ingredient name!"); + throw new ParserException(ErrorMessages.NAME_CANNOT_CONTAIN_SPECIAL_CHAR); + } + + //unusual case + //user input repeated ingredient name for add dish command + if (isExcludeRepeatedIngredients && isRepeatedIngredientName(ingredientName, ingredients)) { + logger.log(Level.WARNING, "Repeated ingredient name for AddDishCommand!"); + throw new ParserException(ErrorMessages.REPEATED_INGREDIENT_NAME); + } + } + + /** + * Converts text of price to float while also checking if the price input is within reasonable range + * + * @param priceText text input for price argument + * @return price in float format + * @throws ParserException if price is not within reasonable format and range + */ + public static float parsePriceToFloat(String priceText) throws ParserException { + String trimmedPriceText = priceText.trim(); + + final Pattern pricePattern = Pattern.compile(PRICE_INPUT_REGEX); + Matcher priceMatcher = pricePattern.matcher(trimmedPriceText); + + // Check whether price text is empty + if (priceText.isEmpty()) { + logger.log(Level.WARNING, "Missing dish price!"); + throw new ParserException(ErrorMessages.MISSING_PRICE); + } else if (!priceMatcher.matches()) { + logger.log(Level.WARNING, "Exceed price valid range!"); + throw new ParserException(ErrorMessages.WRONG_PRICE_TYPE_FOR_EDIT_PRICE); + } + + float price; + try { + price = Float.parseFloat(trimmedPriceText); + } catch (NumberFormatException e) { + logger.log(Level.WARNING, e.getMessage(), e); + throw new ParserException(ErrorMessages.WRONG_PRICE_TYPE_FOR_EDIT_PRICE); + } + + // Specify max and min value for price + float maxPriceValue = (float) 1000000.00; + float minPriceValue = (float) 0; + + // Check whether the price has up to 2 decimal place + if (price > maxPriceValue) { + throw new ParserException(ErrorMessages.LARGE_PRICE_MESSAGE); + } else if (price < minPriceValue) { + throw new ParserException(ErrorMessages.NEGATIVE_PRICE_MESSAGE); + } + + return price; + } + + /** + * Checks in the menu if the dish name already exists. + * + * @param inputDishName dish name entered by the user + * @param menu contains all the existing Dishes + * @return true if dish name already exists in menu, false otherwise + * @throws NullPointerException if the input string is null + */ + public static boolean isRepeatedDishName(String inputDishName, Menu menu) throws NullPointerException { + if (inputDishName == null) { + throw new NullPointerException(); + } + + for (Dish dish: menu.getMenuItemsList()) { + String menuDishName = dish.getName(); + + if (menuDishName.equalsIgnoreCase(inputDishName)) { + return true; + } + } + + return false; + } + + /** + * Checks in the ingredient list if the ingredient name already exists. + * + * @param inputName dish name entered by the user + * @param ingredients contains all the existing Ingredients + * @return true if ingredient name already exists in menu, false otherwise + * @throws NullPointerException if the input string is null + */ + public static boolean isRepeatedIngredientName(String inputName, ArrayList ingredients) + throws NullPointerException { + if (inputName == null) { + throw new NullPointerException(); + } + + for (Ingredient ingredient: ingredients) { + String ingredientNameLowerCase = ingredient.getName().toLowerCase(); + String inputIngredientNameLowerCase = inputName.toLowerCase(); + + if (ingredientNameLowerCase.equals(inputIngredientNameLowerCase)) { + return true; + } + } + + return false; + } + + /** + * Checks the length of the name is too long + * + * @param inputName name + * @return true if the name is more than max character limit set, false otherwise + * @throws NullPointerException if the input string is null + */ + public static boolean isNameLengthInvalid(String inputName) throws NullPointerException { + int maxNameLength = 35; + + if (inputName == null) { + throw new NullPointerException(); + } else if (inputName.length() > maxNameLength) { + return true; + } + + return false; + } + + /** + * Checks if the argument is entered more than once. + * + * @param inputText text to be checked + * @param argument argument to be checked for multiple occurrences + * @return true if there is > 1 match of the argument, false otherwise + */ + public static boolean isRepeatedArgument(String inputText, String argument) { + if (inputText == null || argument == null) { + throw new NullPointerException(ErrorMessages.NULL_STRING_IN_REPEAT_ARGUMENT); + } + + Pattern argumentPattern = Pattern.compile(argument); + Matcher matcher = argumentPattern.matcher(inputText); + int maxNumberOfMatches = 1; + + long argumentMatches = matcher.results().count(); + if (argumentMatches > maxNumberOfMatches) { + return true; + } + + return false; + } + + //@@author NaychiMin + /** + * Parses arguments in the context of the ListIngredient command. + * + * @param menu menu of the current session + * @param arguments string that matches group arguments + * @return the prepared command + */ + private static Command prepareListIngredient(Menu menu, String arguments, Ui ui) { + final Pattern prepareListPattern = Pattern.compile(LIST_INGREDIENTS_ARGUMENT_STRING); + Matcher matcher = prepareListPattern.matcher(arguments.trim()); + + if (!matcher.matches()) { + logger.warning("Unmatched regex!"); + return new IncorrectCommand(ErrorMessages.MISSING_ARGUMENT_FOR_LIST_INGREDIENTS + + ListIngredientCommand.MESSAGE_USAGE, ui); + } + + try { + int dishIndex = Integer.parseInt(matcher.group(1).trim()); + + if (dishIndex < 0) { + throw new Exception(); + } + + if (!menu.isValidDishIndex(dishIndex)) { + return new IncorrectCommand(ErrorMessages.UNLISTED_DISH, ui); + } + + return new ListIngredientCommand(dishIndex, menu, ui); + } catch (Exception e) { + return new IncorrectCommand(ErrorMessages.INVALID_DISH_INDEX_TO_LIST, ui); + } + } + + //@@author ShaniceTang + /** + * Parses arguments in the context of the Delete command. + * + * @param menu menu of the current session + * @param arguments string that matches group arguments + * @return DeleteDishCommand if command is valid, IncorrectCommand otherwise + */ + private static Command prepareDelete(Menu menu, String arguments, Ui ui) { + Pattern deleteDishArgumentsPattern = Pattern.compile(DELETE_ARGUMENT_STRING); + Matcher matcher = deleteDishArgumentsPattern.matcher(arguments.trim()); + + // Checks whether the overall pattern of delete price arguments is correct + if (!matcher.matches()) { + logger.warning("Unmatched regex!"); + return new IncorrectCommand(ErrorMessages.MISSING_ARGUMENT_FOR_DELETE, ui); + } + + int listIndexArgGroup = 1; + + try { + int dishIndex = Integer.parseInt(matcher.group(listIndexArgGroup)); + if (!menu.isValidDishIndex(dishIndex)) { + return new IncorrectCommand(ErrorMessages.INVALID_DISH_INDEX, ui); + } + return new DeleteDishCommand(dishIndex, menu, ui); + } catch (NumberFormatException e) { + return new IncorrectCommand(ErrorMessages.DISH_INDEX_NOT_INT, ui); + } + } + + /** + * Prepares a command to view the total stock in the pantry. + * + * @param ui The user interface to interact with the user. + * @param pantry The pantry containing ingredient stock information. + * @return A command to view the total stock. + */ + private static Command prepareViewTotalStock(Ui ui, Pantry pantry) { + return new ViewTotalStockCommand(pantry, ui); + } + + /** + * Prepares a command to buy ingredients based on the provided arguments. + * + * @param arguments The user input arguments for buying ingredients. + * @param ui The user interface to interact with the user. + * @param pantry The pantry to update with bought ingredients. + * @param menu The menu containing information about available dishes. + * @return A command to buy ingredients or an incorrect command if arguments are invalid. + */ + private static Command prepareBuyIngredient(String arguments, Ui ui, Pantry pantry, Menu menu) { + Pattern buyIngredientArgumentsPattern = Pattern.compile(BUY_INGREDIENT_ARGUMENT_STRING); + Matcher matcher = buyIngredientArgumentsPattern.matcher(arguments.trim()); + + if (!matcher.matches()) { + logger.warning("Unmatched regex!"); + return new IncorrectCommand(ErrorMessages.INVALID_INGREDIENT_ARGUMENTS, ui); + } + + String ingredientsListString = matcher.group(0); + + try { + ArrayList ingredients = parseIngredients(ingredientsListString, false, menu); + return new BuyIngredientCommand(ingredients, ui, pantry); + } catch (NumberFormatException e) { + return new IncorrectCommand(ErrorMessages.INVALID_INGREDIENT_ARGUMENTS, ui); + } catch (Exception e) { + return new IncorrectCommand(e.getMessage(), ui); + } + } + + public static boolean isValidUnit(String ingredientUnit) { + return ingredientUnit.equals(GRAMS_UNIT) || ingredientUnit.equals(ML_UNIT); + } + + public static boolean isEmptyUnit(String ingredientUnit) { + return ingredientUnit.equals(""); + } + + public static boolean isInvalidQty(int ingredientQty) { + return ingredientQty < MIN_QTY || ingredientQty > MAX_QTY; + } + + /** + * Checks for mismatched units between a new ingredient and existing ingredients in the menu. + * + * @param menu The menu containing information about available dishes. + * @param newIngredient The new ingredient to check for mismatched units. + * @throws ParserException If a mismatch in units is detected. + */ + public static void checkForMismatchUnit(Menu menu, Ingredient newIngredient) throws ParserException { + logger.info("Checking for mismatched units..."); + ArrayList dishArrayList = menu.getMenuItemsList(); + + for (Dish dish : dishArrayList) { + traverseIngredientsOfDish(newIngredient, dish); + } + } + + private static void traverseIngredientsOfDish(Ingredient newIngredient, Dish dish) throws ParserException { + ArrayList ingredientArrayList = dish.getIngredients(); + + for (Ingredient currentIngredient : ingredientArrayList) { + logger.info("Comparing name: " + newIngredient.getName() + " and " + currentIngredient.getName()); + compareIngredientName(newIngredient, currentIngredient); + } + } + + private static void compareIngredientName(Ingredient newIngredient, + Ingredient currentIngredient) throws ParserException { + if (currentIngredient.getName().equalsIgnoreCase(newIngredient.getName())) { + logger.info("Comparing units: " + newIngredient.getUnit() + " and " + currentIngredient.getUnit()); + compareUnits(newIngredient, currentIngredient); + } + } + + private static void compareUnits(Ingredient newIngredient, Ingredient currentIngredient) throws ParserException { + if (!currentIngredient.getUnit().equalsIgnoreCase(newIngredient.getUnit())) { + logger.warning("Units not matching!"); + throw new ParserException(newIngredient.getName() + + ErrorMessages.UNIT_NOT_MATCHING + + currentIngredient.getUnit() + + ErrorMessages.RETYPE_COMMAND_MESSAGE); + } + } + + //@@author ziyi105 + /** + * Check whether a text contains special character + * + * @param text text to be checked + * @return true if it contains special character, false otherwise + */ + public static boolean containsSpecialChar(String text) { + Pattern pattern = Pattern.compile("[^a-z0-9 ]", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(text); + return matcher.find(); + } + + //@@author ziyi105 + private static Command prepareHelpCommand(Ui ui) { + return new HelpCommand(ui); + } + + //@@author Cazh1 + /** + * Parses arguments in the context of the AddOrder command. + * + * @param menu menu of the current session + * @param arguments string that matches group arguments + * @param ui + * @return AddOrderCommand if command is valid, IncorrectCommand otherwise + */ + private static Command prepareOrder(Menu menu, String arguments, Ui ui, + Pantry pantry, Sales sales, CurrentDate currentDate) { + final Pattern addOrderArgumentPatter = Pattern.compile(ADD_ORDER_ARGUMENT_STRING); + Matcher matcher = addOrderArgumentPatter.matcher(arguments); + + // Checks whether the overall pattern of add order arguments is correct + if (!matcher.matches()) { + logger.warning("Unmatching regex!"); + return new IncorrectCommand(ErrorMessages.INVALID_ADD_ORDER_FORMAT_MESSAGE + + AddOrderCommand.MESSAGE_USAGE, ui); + } + + OrderList orderList = setOrderList(currentDate, sales); + + try { + // To retrieve specific arguments from arguments + String dishName = matcher.group(DISH_NAME_MATCHER_GROUP_NUM).trim(); + int dishQty = parseQtyToInt(matcher.group(ORDER_QTY_MATCHER_GROUP_NUM).trim()); + + detectErrorPostDishNameParse(dishName, menu, false); + + Dish orderedDish = menu.getDishFromName(dishName); + if (orderedDish == null) { + return new IncorrectCommand(ErrorMessages.DISH_NOT_FOUND, ui); + } + + Order order = new Order(orderedDish, dishQty); + + return new AddOrderCommand(order, ui, pantry, orderList, menu); + } catch (ParserException e) { + return new IncorrectCommand(e.getMessage(), ui); + } catch (NumberFormatException e) { + return new IncorrectCommand(ErrorMessages.INVALID_INT_ORDER_QTY, ui); + } catch (Exception e) { + return new IncorrectCommand(ErrorMessages.INVALID_ADD_ORDER_FORMAT_MESSAGE + + AddOrderCommand.MESSAGE_USAGE + e.getMessage(), ui); + } + } + + /** + * Prepares PreviousDayCommand + * + * @param ui ui object of the current session + * @param currentDate currentDate object of the current session + * @return PreviousDayCommand if after day 1, IncorrectCommand if before + */ + private static Command preparePreviousDay(Ui ui, CurrentDate currentDate) { + int currentDay = currentDate.getCurrentDay(); + if (currentDay == 0) { + return new IncorrectCommand(Messages.PREVIOUS_DAY_TIME_TRAVEL, ui); + } + return new PreviousDayCommand(ui, currentDate); + } + + /** + * Prepares NextDayCommand + * + * @param ui ui object of the current session + * @param sales sales object of the current session + * @param currentDate currentDate object of the current session + * @return NextDayCommand + */ + private static Command prepareNextDay(Ui ui, Sales sales, CurrentDate currentDate) { + return new NextDayCommand(ui, sales, currentDate); + } + + //@@author DextheChik3n + /** + * Parses the quantity text string into integer and checks if the input is valid + * + * @param qtyText text that consist of the order dish quantity + * @return int value of the quantity + * @throws ParserException if the input string does not match the constraints + */ + public static int parseQtyToInt(String qtyText) throws ParserException { + if (qtyText.isEmpty()) { + throw new ParserException(ErrorMessages.MISSING_ORDER_QTY); + } + + int dishQty = Integer.parseInt(qtyText); + + int maxDishQty = 10000; + int minDishQty = 1; + + if (dishQty < minDishQty) { + throw new ParserException(ErrorMessages.BELOW_MIN_ORDER_QTY); + } else if (dishQty > maxDishQty) { + throw new ParserException(ErrorMessages.EXCEED_MAX_ORDER_QTY); + } + + return dishQty; + } + + //@@author NaychiMin + /** + * Prepares a command to display all sales items. + * + * @param sale The Sales object containing sales data. + * @param ui The Ui object for user interface interactions. + * @return A ShowSalesCommand instance for viewing all sales items. + */ + private static Command prepareShowSales(Sales sale, Ui ui, String arguments) { + if (arguments.isEmpty()) { + return new ListTotalSalesCommand(sale, ui); + } else { + return new IncorrectCommand(ErrorMessages.WRONG_LIST_TOTAL_SALES_FORMAT, ui); + } + + } + + /** + * Prepares a command to display sales items for a specific day. + * + * @param arguments The arguments containing the day for which sales are to be displayed. + * @param ui The Ui object for user interface interactions. + * @param sales The Sales object containing sales data. + * @return A ShowSalesByDayCommand instance for viewing sales items on a specific day. + */ + private static Command prepareShowSalesByDay(String arguments, Ui ui, Sales sales) { + final Pattern showSaleByDayPattern = Pattern.compile(SHOW_SALE_BY_DAY_ARGUMENT_STRING); + Matcher matcher = showSaleByDayPattern.matcher(arguments.trim()); + + if (!matcher.matches()) { + logger.warning("Unmatching regex!"); + return new IncorrectCommand(ErrorMessages.INVALID_SHOW_SALE_DAY_FORMAT_MESSAGE + + ListSaleByDayCommand.MESSAGE_USAGE, ui); + } + + try { + int day = Integer.parseInt(matcher.group(1).trim()); + if (day < 0) { + throw new Exception(); + } + return new ListSaleByDayCommand(day, ui, sales); + } catch (Exception e) { + return new IncorrectCommand(ErrorMessages.INVALID_DAY_FORMAT, ui); + } + } + + //@@author Cazh1 + /** + * Sets the orderList according to the Day + * + * @param currentDate currentDate object of the current session + * @param sales sales object of the current session, contains the orderLists + * @return The respective orderList + */ + private static OrderList setOrderList(CurrentDate currentDate, Sales sales) { + int currentDay = currentDate.getCurrentDay(); + return sales.getOrderList(currentDay); + } +} diff --git a/src/main/java/seedu/cafectrl/parser/ParserUtil.java b/src/main/java/seedu/cafectrl/parser/ParserUtil.java new file mode 100644 index 0000000000..ac7d5898e7 --- /dev/null +++ b/src/main/java/seedu/cafectrl/parser/ParserUtil.java @@ -0,0 +1,16 @@ +package seedu.cafectrl.parser; + +import seedu.cafectrl.command.Command; +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.ui.Ui; + +/** + * Parser interface for external class to use Parser + */ +public interface ParserUtil { + Command parseCommand(Menu menu, String userInput, Ui ui, + Pantry pantry, Sales sales, CurrentDate currentDate); +} diff --git a/src/main/java/seedu/cafectrl/parser/exception/ParserException.java b/src/main/java/seedu/cafectrl/parser/exception/ParserException.java new file mode 100644 index 0000000000..05eebe2303 --- /dev/null +++ b/src/main/java/seedu/cafectrl/parser/exception/ParserException.java @@ -0,0 +1,12 @@ +package seedu.cafectrl.parser.exception; +/** + * Represents a parse error encountered by parser. + */ +public class ParserException extends Exception { + /** + * @param errorMessage contains relevant information on failed constraint(s) + */ + public ParserException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/seedu/cafectrl/storage/Decoder.java b/src/main/java/seedu/cafectrl/storage/Decoder.java new file mode 100644 index 0000000000..47c21be8ec --- /dev/null +++ b/src/main/java/seedu/cafectrl/storage/Decoder.java @@ -0,0 +1,362 @@ +package seedu.cafectrl.storage; + + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.parser.Parser; +import seedu.cafectrl.parser.exception.ParserException; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * The Decoder class offers methods to interpret string representations from text files, + * decoding them into appropriate data structures. It includes methods to decode a Menu, + * Pantry stock, and OrderList, allowing retrieval of data stored in a file. + */ +public class Decoder { + private static final Ui ui = new Ui(); + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private static final String DIVIDER = "\\| "; + private static final String INGREDIENT_DIVIDER = " - "; + + /** For menu decoder */ + private static final int MAX_INGREDIENTS_STRING_ARRAY_SIZE = 1; + private static final int DISH_NAME_INDEX_DISH_ARRAY = 0; + private static final int DISH_PRICE_INDEX_DISH_ARRAY = 1; + private static final int DISH_INGREDIENT_START_INDEX = 2; + private static final int NAME_INDEX_INGREDIENT_ARRAY = 0; + private static final int QTY_INDEX_INGREDIENT_ARRAY = 1; + private static final int UNIT_INDEX_INGREDIENT_ARRAY = 2; + + /** for stock pantry decoder */ + private static final int NAME_INDEX_PANTRY = 0; + private static final int QTY_INDEX_PANTRY = 1; + private static final int UNIT_INDEX_PANTRY = 2; + private static final int MAX_PANTRY_ARRAY_SIZE = 3; + + /** for sales decoder */ + private static final int DAY_INDEX_SALES = 0; + private static final int DISH_NAME_INDEX_SALES = 1; + private static final int QTY_INDEX_SALES = 2; + private static final int DISH_PRICE_INDEX_SALES = 3; + private static final int STATUS_INDEX_SALES = 4; + private static final String TRUE_STRING = "true"; + private static final String FALSE_STRING = "false"; + private static final int MIN_DISH_PRICE = 0; + + //@@author ShaniceTang + /** + * Decodes an ArrayList of string lines into a Menu object, reconstructing its content. + * + * @param textLines An ArrayList of strings representing the encoded Menu data. + * @return A Menu object containing the decoded Menu data. + */ + public static Menu decodeMenuData(ArrayList textLines) { + logger.info("Decoding menu.txt to Menu..."); + ArrayList menuDishList = new ArrayList<>(); + + for(String dishString : textLines) { + logger.info("Line to decode: " + dishString); + decodeDishString(dishString, menuDishList); + } + + return new Menu(menuDishList); + } + + /** + * Decodes a string representation of a dish and adds it to the menu's list of dishes. + * + * @param dishString The string containing dish information. + * @param menuDishList The list to which the decoded dish will be added. + */ + private static void decodeDishString(String dishString, ArrayList menuDishList) { + String dishName = ""; + try { + String[] dishStringArray = dishString.split(DIVIDER); + dishName = dishStringArray[DISH_NAME_INDEX_DISH_ARRAY].trim().toLowerCase(); + + checkNameValidity(dishName); + + float dishPrice = Parser.parsePriceToFloat(dishStringArray[DISH_PRICE_INDEX_DISH_ARRAY]); + String[] ingredientStringArray = Arrays.copyOfRange( + dishStringArray, DISH_INGREDIENT_START_INDEX, dishStringArray.length); + ArrayList ingredientsList = decodeIngredientData(ingredientStringArray); + + menuDishList.add(new Dish(dishName, ingredientsList, dishPrice)); + } catch (ParserException e) { + logger.log(Level.WARNING, "Dish has invalid price: " + e.getMessage(), e); + ui.showToUser(ErrorMessages.INVALID_MENU_DATA + dishString); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "Dish has no ingredients: " + e.getMessage(), e); + ui.showToUser(e.getMessage() + dishName); + } catch (Exception e) { + logger.log(Level.WARNING, "Line corrupted: " + e.getMessage(), e); + ui.showToUser(ErrorMessages.INVALID_MENU_DATA + dishString); + } + } + + private static void checkNameValidity(String name) throws Exception { + if (Parser.isNameLengthInvalid(name) || name.isEmpty() || Parser.containsSpecialChar(name)) { + throw new Exception(); + } + } + + /** + * Decodes an array of strings representing ingredient data into a list of Ingredient objects. + * + * @param ingredientsStringArray An array of strings containing encoded ingredient data. + * @return An ArrayList of Ingredient objects containing the decoded ingredient information. + */ + private static ArrayList decodeIngredientData(String[] ingredientsStringArray) throws Exception { + ArrayList ingredientList = new ArrayList<>(); + + if (ingredientsStringArray.length < MAX_INGREDIENTS_STRING_ARRAY_SIZE) { + throw new RuntimeException(ErrorMessages.MISSING_INGREDIENT_MENU_DATA); + } + + for(String ingredientString : ingredientsStringArray) { + logger.info("Ingredient to decode: " + ingredientString); + + String[] array = ingredientString.split(INGREDIENT_DIVIDER); + String name = array[NAME_INDEX_INGREDIENT_ARRAY].trim().toLowerCase(); + checkNameValidity(name); + + int qty = Integer.parseInt(array[QTY_INDEX_INGREDIENT_ARRAY].trim()); + checkQtyValidity(qty); + + String unit = array[UNIT_INDEX_INGREDIENT_ARRAY].trim(); + checkUnitValidity(unit); + + ingredientList.add(new Ingredient(name, qty, unit)); + } + return ingredientList; + } + + private static void checkQtyValidity(int qty) throws Exception { + if (Parser.isInvalidQty(qty)) { + throw new Exception(); + } + } + + private static void checkUnitValidity(String unit) throws Exception { + if (!Parser.isValidUnit(unit) || Parser.isEmptyUnit(unit)) { + throw new Exception(); + } + } + + //@@author ziyi105 + /** + * Decodes raw string from pantry stock data file and create ingredient object from the data + * + * @param encodedPantryStock raw string from pantry stock data file + * @return a new pantry object with data from the pantry stock data file + */ + public static Pantry decodePantryStockData(ArrayList encodedPantryStock) { + logger.info("Decoding Pantry_stock.txt to PantryStock..."); + ArrayList pantryStock = new ArrayList<>(); + Ingredient ingredient; + + if (encodedPantryStock.isEmpty()) { + return new Pantry(ui); + } + for (String encodedData : encodedPantryStock) { + logger.info("Line to decode: " + encodedData); + String[] decodedData = encodedData.split(DIVIDER); + if (!isValidPantryStockFormat(decodedData)) { + ui.showToUser(ErrorMessages.ERROR_IN_PANTRY_STOCK_DATA + encodedData); + continue; + } + String ingredientName = decodedData[NAME_INDEX_PANTRY].trim().toLowerCase(); + String qtyText = decodedData[QTY_INDEX_PANTRY].trim(); + String unit = decodedData[UNIT_INDEX_PANTRY].trim(); + + // Check whether qty is an integer + int qty; + try { + qty = Integer.parseInt(qtyText); + } catch (NumberFormatException e) { + logger.log(Level.WARNING, "Line corrupted: " + e.getMessage(), e); + ui.showToUser(ErrorMessages.ERROR_IN_PANTRY_STOCK_DATA + encodedData); + continue; + } + + // Check whether the parameters are correct + if (isValidIngredientName(ingredientName, pantryStock) + && !Parser.isInvalidQty(qty) + && isValidUnit(unit)) { + ingredient = new Ingredient(ingredientName, qty, unit); + pantryStock.add(ingredient); + } else { + logger.info(ErrorMessages.ERROR_IN_PANTRY_STOCK_DATA + encodedData); + ui.showToUser(ErrorMessages.ERROR_IN_PANTRY_STOCK_DATA + encodedData); + } + } + + return new Pantry(ui, pantryStock); + } + + /** + * Checks whether the ingredient name is valid in terms of length, containment of + * special character and whether it is a repeated ingredient + * @param ingredientName name of the ingredient + * @param pantryStock pantry stock with data from previous lines in the text file + * @return true if the name is valid, false otherwise + */ + private static boolean isValidIngredientName(String ingredientName, ArrayList pantryStock) { + return !Parser.containsSpecialChar(ingredientName) + && !Parser.isNameLengthInvalid(ingredientName) + && !Parser.isRepeatedIngredientName(ingredientName, pantryStock); + } + + private static boolean isValidUnit(String unit) { + return !Parser.isEmptyUnit(unit) && Parser.isValidUnit(unit); + } + + /** + * Checks whether the pantry stock is in the format of ingredient name | quantity (int) | unit + * + * @param decodedPantryStock string array of the raw data string from pantry stock data file + * split with "|" + * @return true if the format is correct, false otherwise + */ + private static boolean isValidPantryStockFormat(String[] decodedPantryStock) { + if (decodedPantryStock.length != MAX_PANTRY_ARRAY_SIZE) { + return false; + } else { + try { + Integer.parseInt(decodedPantryStock[QTY_INDEX_PANTRY].trim()); + } catch (NumberFormatException e) { + return false; + } + } + return true; + } + + //@@author NaychiMin + /** + * Decodes a list of order data and constructs a Sales object using an array of OrderList objects. + * + * @param textLines List of order strings in the format "dishName|quantity|totalOrderCost". + * @param menu Menu instance to retrieve Dish objects based on dishName. + * @return Sales object containing OrderList objects decoded from the provided strings. + */ + public static Sales decodeSales(ArrayList textLines, Menu menu) { + logger.info("Decoding orders.txt to Sales..."); + boolean salesOrderTextTamperDetectionMessagePrinted = false; + ArrayList orderLists = new ArrayList<>(); + + if (textLines.isEmpty()) { + return new Sales(); + } + + //for each 'order' in text file + for (String line : textLines) { + logger.info("Line to decode: " + line); + if (line.isEmpty()) { + continue; + } + decodeSalesData(line, orderLists, menu); + } + + if (orderLists.isEmpty()) { + return new Sales(); + } + return new Sales(orderLists); + } + + /** + * Decodes the sales data from a single order line and adds it to the list of OrderList objects. + * + * @param orderLine The order line in the format "day|dishName|quantity|totalOrderCost|isComplete". + * @param orderLists The list of OrderList objects to which the decoded order will be added. + * @param menu Menu instance to retrieve Dish objects based on dishName. + */ + private static void decodeSalesData(String orderLine, ArrayList orderLists, Menu menu) { + try { + String[] orderData = orderLine.split(DIVIDER); + int day = Integer.parseInt(orderData[DAY_INDEX_SALES].trim()) - Sales.DAY_DISPLAY_OFFSET; + String dishName = orderData[DISH_NAME_INDEX_SALES].trim().toLowerCase(); + + //@@author Cazh1 + //keeps track of the number of days cafe has been operating for + if (dishName.equals(Encoder.NULL_ORDER_DAY)) { + fillOrderListSize(orderLists, day); + return; + } + //@@author + + int quantity = Integer.parseInt(orderData[QTY_INDEX_SALES].trim()); + float decodedDishPrice = Float.parseFloat(orderData[DISH_PRICE_INDEX_SALES].trim()); + String completeStatus = orderData[STATUS_INDEX_SALES].trim(); + float totalOrderCost = quantity * decodedDishPrice; + + checkNameValidity(dishName); + boolean isDataAccurate = isCompleteStatusAccurate(orderLine, completeStatus) + && isValidQty(orderLine, quantity) + && isValidPrice(orderLine, decodedDishPrice); + if (!isDataAccurate) { + return; + } + + Dish dishToAdd = new Dish(dishName, decodedDishPrice); + //creates new order and adds to orderList for specific day + boolean isComplete = Boolean.parseBoolean(completeStatus.toLowerCase()); + Order orderedDish = new Order(dishToAdd, quantity, totalOrderCost, isComplete); + fillOrderListSize(orderLists, day); + orderLists.get(day).addOrder(orderedDish); + } catch (Exception e) { + logger.log(Level.WARNING, "Line corrupted: " + e.getMessage(), e); + ui.showToUser(ErrorMessages.INVALID_SALES_DATA + orderLine); + } + } + + private static boolean isCompleteStatusAccurate(String orderLine, String completeStatus) { + if (completeStatus.equalsIgnoreCase(TRUE_STRING) + || completeStatus.equalsIgnoreCase(FALSE_STRING)) { + return true; + } + ui.showToUser(ErrorMessages.INVALID_ORDER_STATUS + orderLine); + return false; + } + + private static boolean isValidPrice(String orderLine, Float decodedDishPrice) { + if (decodedDishPrice >= MIN_DISH_PRICE) { + return true; + } + ui.showToUser(ErrorMessages.INVALID_DISH_PRICE + orderLine); + return false; + } + + private static boolean isValidQty(String orderLine, int quantity) { + if (quantity > 0) { + return true; + } + ui.showToUser(ErrorMessages.INVALID_ORDER_QTY + orderLine); + return false; + } + + //@@author Cazh1 + /** + * Increases the size of the orderlist when there is gap between the previous order and the next + * + * @param orderLists The current partially filled ArrayList of OrderList + * @param day The day of the next order + */ + private static void fillOrderListSize(ArrayList orderLists, int day) { + while (orderLists.size() <= day) { + orderLists.add(new OrderList()); + } + } +} diff --git a/src/main/java/seedu/cafectrl/storage/Encoder.java b/src/main/java/seedu/cafectrl/storage/Encoder.java new file mode 100644 index 0000000000..f6d7b76a20 --- /dev/null +++ b/src/main/java/seedu/cafectrl/storage/Encoder.java @@ -0,0 +1,189 @@ +package seedu.cafectrl.storage; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.data.Menu; + +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * The Encoder class provides methods to encode various data structures into string representations to + * be written into their respective text files. + * It includes methods to encode a Menu, Pantry stock, and OrderList, + * making the data suitable for saving to a file. + */ +public class Encoder { + public static final String NULL_ORDER_DAY = "the last day has no orders but please account for it"; + private static final String DIVIDER = " | "; + private static final String INGREDIENT_DIVIDER = " - "; + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private static final String LINE_BREAK = "\n"; + private static final String EMPTY_STRING = ""; + private static final String CARRIAGE_RETURN = "\r"; + private static final String TWO_DECIMAL_PLACE_FORMAT = "%.2f"; + + //@@author Cazh1 + /** + * Generates a hash for the content to be saved into text save file + * + * @param stringArrayList The arraylist of String to be saved into text save file + * @return arraylist of String with the generated hash + */ + private static ArrayList hashEncoding(ArrayList stringArrayList) { + String stringArrayListAsString = String.join(", ", stringArrayList).trim(); + + //The generated String has line breaks, this removes line breaks + String stringArrayListAsStringInOneLine = stringArrayListAsString + .replace(LINE_BREAK, EMPTY_STRING) + .replace(CARRIAGE_RETURN, EMPTY_STRING); + + //Generate Hash from content + int stringArrayListHash = stringArrayListAsStringInOneLine.hashCode(); + String stringArrayListHashAsString = String.valueOf(stringArrayListHash); + + //Adds generated Hash into the original ArrayList + stringArrayList.add(stringArrayListHashAsString); + return stringArrayList; + } + + //@@author ShaniceTang + /** + * Encodes a Menu object into a list of strings representing its contents, suitable for saving to a file. + * + * @param menu The Menu object to encode into a string representation. + * @return An ArrayList of strings, where each string represents a Dish in the Menu. + */ + public static ArrayList encodeMenu(Menu menu) { + logger.info("Encoding Menu to menu.txt..."); + ArrayList menuStringList = new ArrayList<>(); + ArrayList menuDishList = menu.getMenuItemsList(); + for(Dish dish : menuDishList) { + StringBuilder dishString = new StringBuilder(); + dishString.append(dish.getName() + DIVIDER); + dishString.append(dish.getPrice()); + dishString.append(encodeIngredientList(dish.getIngredients())); + dishString.append(System.lineSeparator()); + menuStringList.add(String.valueOf(dishString)); + logger.info("Encoded dish: " + dishString); + } + ArrayList menuStringListHashed = hashEncoding(menuStringList); + return menuStringListHashed; + } + + /** + * Encodes a list of ingredients into a StringBuilder for inclusion in the menu encoding. + * + * @param ingredientList The list of Ingredient objects to encode. + * @return A StringBuilder containing the encoded representation of the ingredient list. + */ + private static StringBuilder encodeIngredientList(ArrayList ingredientList) { + StringBuilder ingredientListString = new StringBuilder(); + for(Ingredient ingredient : ingredientList) { + ingredientListString.append(DIVIDER); + ingredientListString.append(ingredient.getName() + INGREDIENT_DIVIDER); + ingredientListString.append(ingredient.getQty() + INGREDIENT_DIVIDER); + ingredientListString.append(ingredient.getUnit()); + } + return ingredientListString; + } + + //@@author ziyi105 + /** + * Encodes the pantry stock into format ingredient name | quantity | unit for storage + * + * @param pantry the pantry from current session + * @return an arrayList of string of ecoded pantry stock + */ + public static ArrayList encodePantryStock(Pantry pantry) { + // Convert pantry stock to a list of String + ArrayList pantryStockInString = new ArrayList<>(); + ArrayList pantryStock = pantry.getPantryStock(); + + for (Ingredient ingredient : pantryStock) { + StringBuilder encodedIngredient = new StringBuilder(); + encodedIngredient.append(ingredient.getName().trim()); + encodedIngredient.append(DIVIDER); + encodedIngredient.append(ingredient.getQty()); + encodedIngredient.append(DIVIDER); + encodedIngredient.append(ingredient.getUnit()); + encodedIngredient.append(System.lineSeparator()); + pantryStockInString.add(encodedIngredient.toString()); + logger.info("Encoded ingredient: " + ingredient.getName()); + } + ArrayList pantryStockInStringHashed = hashEncoding(pantryStockInString); + return pantryStockInStringHashed; + } + + //@@author NaychiMin + /** + * Encodes a Sales object into a list of strings for storage. + * Each string represents an order, including day, dish name, quantity, and total cost. + * + * @param sales The Sales object to be encoded. + * @return An ArrayList of strings representing the encoded sales data. + */ + public static ArrayList encodeSales(Sales sales) { + logger.info("Encoding Sales to orders.txt..."); + ArrayList encodedList = new ArrayList<>(); + ArrayList orderLists = sales.getOrderLists(); + + for (int day = 0; day < orderLists.size(); day++) { + logger.info("Encoding sales of day " + day); + + //get orderList for each day from list of sales + OrderList orderList = sales.getOrderList(day); + + //get order from each orderList obtained + for (Order order : orderList.getOrderList()) { + StringBuilder orderString = new StringBuilder(); + + //day of each orderList is index + 1 + float orderedDishPrice = order.getOrderedDish().getPrice(); + String orderedDishPriceString = String.format(TWO_DECIMAL_PLACE_FORMAT, orderedDishPrice); + + orderString.append((day + 1) + DIVIDER); + orderString.append(order.getDishName() + DIVIDER); + orderString.append(order.getQuantity() + DIVIDER); + orderString.append(orderedDishPriceString + DIVIDER); + orderString.append(order.getIsComplete()); + orderString.append(System.lineSeparator()); + encodedList.add(String.valueOf(orderString)); + logger.info("Encoded order: " + orderString); + } + + if (day == sales.getDaysAccounted()) { + encodedList = encodeLastSalesDay(encodedList, orderList, day); + } + } + ArrayList encodedListHashed = hashEncoding(encodedList); + return encodedListHashed; + } + + //@@author Cazh1 + /** + * Checks if the last day accessed has valid orders added + * + * @param encodedList An ArrayList of strings representing the encoded sales data. + * @param orderList An ArrayList of Orders of the last day accessed + * @param day The last day accessed + * @return encodedList with specific String added at the end if no valid orders were detected + */ + private static ArrayList encodeLastSalesDay(ArrayList encodedList, OrderList orderList, int day) { + if (orderList.getSize() == 0) { + StringBuilder orderString = new StringBuilder(); + + //day of each orderList is index + 1 + orderString.append((day + 1) + DIVIDER); + orderString.append(NULL_ORDER_DAY); + orderString.append(System.lineSeparator()); + encodedList.add(String.valueOf(orderString)); + } + return encodedList; + } +} diff --git a/src/main/java/seedu/cafectrl/storage/FileManager.java b/src/main/java/seedu/cafectrl/storage/FileManager.java new file mode 100644 index 0000000000..9803ee9444 --- /dev/null +++ b/src/main/java/seedu/cafectrl/storage/FileManager.java @@ -0,0 +1,107 @@ +package seedu.cafectrl.storage; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.logging.Logger; + +//@@author DextheChik3n +/** + * Manage everything related to file such as writing, reading, opening and creating file + */ +public class FileManager { + + public static final String USER_BASE_DIRECTORY = "user.dir"; + private static Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + private final Ui ui; + + public FileManager(Ui ui) { + this.ui = ui; + } + + /** + * Reads the text file from the specified file path and stores each line in an ArrayList. + * + * @return ArrayList that consists of every text line in each element + * @throws FileNotFoundException if text file at the specified file path does not exist + */ + public ArrayList readTextFile(String filePath) throws FileNotFoundException { + logger.info("Reading text file..."); + String userWorkingDirectory = System.getProperty(USER_BASE_DIRECTORY); + Path dataFilePath = Paths.get(userWorkingDirectory, filePath); + File textFile = new File(String.valueOf(dataFilePath)); + ArrayList textLines = new ArrayList<>(); + + Scanner s = new Scanner(textFile); + + while (s.hasNext()) { + textLines.add(s.nextLine()); + } + + s.close(); + + return textLines; + } + + /** + * Checks if the text file and folder exists in the user's system and creates them (if needed) + * @param filePath the specified path location of the file + */ + public void checkFileExists(String filePath) throws Exception { + logger.info("Checking if " + filePath + " exists..."); + + if (filePath.isEmpty()) { + throw new Exception(ErrorMessages.MISSING_FILEPATH); + } + + String userWorkingDirectory = System.getProperty(USER_BASE_DIRECTORY); + Path dataFilePath = Paths.get(userWorkingDirectory, filePath); + Path dataFolderPath = dataFilePath.getParent(); + File textFile = new File(String.valueOf(dataFilePath)); + File folder = new File(String.valueOf(dataFolderPath)); + + //Check if data folder exists + if (!Files.exists(dataFolderPath)) { + logger.info("Creating directory " + dataFolderPath + "..."); + folder.mkdir(); + ui.showToUser(ErrorMessages.DATA_FOLDER_NOT_FOUND_MESSAGE, System.lineSeparator()); + } + + //Check if the file at the specified file path exists + if (!Files.exists(dataFilePath)) { + logger.info(filePath + " does not exist, creating new file..."); + textFile.createNewFile(); + } + } + + /** + * Writes a list of texts to the text file at the specified file path. + * Will overwrite all text in text file. + * + * @param filePath file path of the text file. + * @param listOfTextToAdd text to be written to the text file. + */ + public void overwriteFile(String filePath, ArrayList listOfTextToAdd) { + try { + checkFileExists(filePath); + FileWriter fw = new FileWriter(filePath); + for (String line : listOfTextToAdd) { + logger.info("Overwriting " + filePath + " with " + line + "..."); + fw.write(line); + } + + fw.close(); + } catch (Exception e) { + ui.showToUser(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/cafectrl/storage/FilePath.java b/src/main/java/seedu/cafectrl/storage/FilePath.java new file mode 100644 index 0000000000..d72bb4eb97 --- /dev/null +++ b/src/main/java/seedu/cafectrl/storage/FilePath.java @@ -0,0 +1,8 @@ +package seedu.cafectrl.storage; + +//@@author ziyi105 +public class FilePath { + public static final String MENU_FILE_PATH = "data/menu.txt"; + public static final String PANTRY_STOCK_FILE_PATH = "data/pantry_stock.txt"; + public static final String ORDERS_FILE_PATH = "data/orders.txt"; +} diff --git a/src/main/java/seedu/cafectrl/storage/Storage.java b/src/main/java/seedu/cafectrl/storage/Storage.java new file mode 100644 index 0000000000..6124dcd84b --- /dev/null +++ b/src/main/java/seedu/cafectrl/storage/Storage.java @@ -0,0 +1,244 @@ +package seedu.cafectrl.storage; + +import seedu.cafectrl.CafeCtrl; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author ziyi105 +/** + * Handles loading and saving data for menu, orderList, pantryStock + */ +public class Storage { + private static final int INDEX_OFFSET_VALUE = 1; + private static final String HASH_REGEX_1 = "^[0-9]+$"; + private static final String HASH_REGEX_2 = "^-[0-9]+$"; + private static final String HASH_REGEX_3 = "^0{2,}$"; + private static final String ENCODE_DELIMITER = ", "; + + private static final Logger logger = Logger.getLogger(CafeCtrl.class.getName()); + protected FileManager fileManager; + protected Ui ui; + private final boolean isHashingEnabled = true; + private boolean isMenuTampered = false; + private boolean isOrdersTampered = false; + private boolean isPantryStockTampered = false; + private boolean isHashStringTampered = false; + private boolean isTamperedMessagePrinted = false; + + public Storage (Ui ui) { + this.fileManager = new FileManager(ui); + this.ui = ui; + } + + //@@author Cazh1 + private boolean isFileEmpty(ArrayList encodedStringArrayList) { + return encodedStringArrayList.isEmpty(); + } + + /** + * Boolean to detect if the text save file has been tampered with + * + * @param encodedStringArrayList The arraylist of string read from text save file + * @return true is the file's hash is not normal or does not match the newly generated hash, false otherwise + */ + private boolean isFileCorrupted(ArrayList encodedStringArrayList) { + //Hash string is stored as last in the ArrayList + int lastIndex = encodedStringArrayList.size() - INDEX_OFFSET_VALUE; + String hashString = encodedStringArrayList.get(lastIndex); + + //Checks if the saved Hash is abnormal + if (((!hashString.matches(HASH_REGEX_1)) && (!hashString.matches(HASH_REGEX_2))) || + hashString.matches(HASH_REGEX_3)) { + return true; + } + + try { + int fileHash = Integer.parseInt(hashString); + //Removes the saved Hash String for decoding + encodedStringArrayList.remove(lastIndex); + + //Prepares String in same format as when encoding, generates Hash from the save file content + String encodedMenuAsString = String.join(ENCODE_DELIMITER, encodedStringArrayList).trim(); + int encodedMenuHash = encodedMenuAsString.hashCode(); + + //Checks if the generated Hash matches the saved Hash + if (encodedMenuHash != fileHash) { + return true; + } + } catch (Exception e) { + isHashStringTampered = true; + logger.log(Level.INFO, "Tampered Hash string"); + return true; + } + return false; + } + + /** + * Detects the areas of the save files that are tampered with + * and prints out respective messages to the user, while minimizing repeats + */ + public void detectTamper() { + if (!isMenuTampered && !isOrdersTampered && !isPantryStockTampered && !isHashStringTampered) { + return; + } + if (!isTamperedMessagePrinted) { + ui.showToUser(Messages.SAVE_FILE_TAMPER_DETECTED); + isTamperedMessagePrinted = true; + } + if (isHashStringTampered) { + ui.showToUser(Messages.HASH_STRING_TAMPERED, Messages.HASH_STRING_MESSAGE); + isHashStringTampered = false; + } + if (isMenuTampered) { + ui.showToUser(Messages.SAVE_FILE_FORMAT_MENU); + isMenuTampered = false; + } + if (isPantryStockTampered) { + ui.showToUser(Messages.SAVE_FILE_FORMAT_PANTRY_STOCK); + isPantryStockTampered = false; + } + if (isOrdersTampered) { + ui.showToUser(Messages.SAVE_FILE_FORMAT_ORDERS); + isOrdersTampered = false; + } + ui.showToUser(""); + } + + //@@author ShaniceTang + /** + * Loads menu data from a text file, decodes it, and returns it as a Menu object. + * + * @return A Menu object containing data from the file. + */ + public Menu loadMenu() { + logger.info("Loading menu..."); + try { + ArrayList encodedMenu = fileManager.readTextFile(FilePath.MENU_FILE_PATH); + if (!isFileEmpty(encodedMenu) && isFileCorrupted(encodedMenu) && isHashingEnabled) { + isMenuTampered = true; + logger.log(Level.INFO, "Tampered Menu file"); + detectTamper(); + } + return Decoder.decodeMenuData(encodedMenu); + } catch (FileNotFoundException e) { + logger.log(Level.WARNING, "menu.txt not found!\n" + e.getMessage(), e); + ui.showToUser(ErrorMessages.MENU_FILE_NOT_FOUND_MESSAGE, System.lineSeparator()); + return new Menu(); + } finally { + ui.showToUser(Messages.DONE_LOADING_MENU); + ui.printLine(); + } + } + + /** + * Encodes the provided menu data and writes it to a text file. + * + * @param menu The Menu object to be saved to the file. + * @throws IOException if the file is not found in the specified file path. + */ + private void saveMenu(Menu menu) throws IOException { + logger.info("Saving menu..."); + fileManager.overwriteFile(FilePath.MENU_FILE_PATH, Encoder.encodeMenu(menu)); + } + + //@@author ziyi105 + /** + * Read and decode pantryStock data from text file and pass it to the menu + * + * @return pantryStock with data from the file + */ + public Pantry loadPantryStock() { + try { + ArrayList encodedPantryStock = fileManager.readTextFile(FilePath.PANTRY_STOCK_FILE_PATH); + if (!isFileEmpty(encodedPantryStock) && isFileCorrupted(encodedPantryStock) && isHashingEnabled) { + isPantryStockTampered = true; + logger.log(Level.INFO, "Tampered Pantry Stock file"); + detectTamper(); + } + return Decoder.decodePantryStockData(encodedPantryStock); + } catch (FileNotFoundException e) { + ui.showToUser(ErrorMessages.PANTRY_FILE_NOT_FOUND_MESSAGE, System.lineSeparator()); + return new Pantry(ui); + } finally { + ui.showToUser(Messages.DONE_LOADING_PANTRY_STOCK); + ui.printLine(); + } + } + + /** + * Encode and write the data from PantryStock to the text file + * + * @param pantry pantry from current session + * @throws IOException if the file is not found in the specified file path + */ + private void savePantryStock(Pantry pantry) throws IOException { + fileManager.overwriteFile(FilePath.PANTRY_STOCK_FILE_PATH, Encoder.encodePantryStock(pantry)); + } + + //@@author NaychiMin + /** + * Loads order lists from a text file, decodes it, and returns it as a Sales object. + * + * @return An OrderList object containing data from the file. + */ + public Sales loadOrderList(Menu menu) { + logger.info("Loading orders..."); + try { + ArrayList encodedOrderList = fileManager.readTextFile(FilePath.ORDERS_FILE_PATH); + if (!isFileEmpty(encodedOrderList) && isFileCorrupted(encodedOrderList) && isHashingEnabled) { + isOrdersTampered = true; + logger.log(Level.INFO, "Tampered Order file"); + detectTamper(); + } + return Decoder.decodeSales(encodedOrderList, menu); + } catch (FileNotFoundException e) { + logger.log(Level.WARNING, "orders.txt not found!\n" + e.getMessage(), e); + ui.showToUser(ErrorMessages.ORDER_LIST_FILE_NOT_FOUND_MESSAGE, System.lineSeparator()); + return new Sales(); + } finally { + ui.showToUser(Messages.DONE_LOADING_SALES); + ui.printLine(); + } + } + + /** + * Encodes the provided OrderList data from Sales object and writes it to a text file + * + * @param sales The Sales object containing the order to be saved to the file. + * @throws IOException if the file is not found in the specified file path. + */ + private void saveOrderList(Sales sales) throws IOException { + logger.info("Saving orders..."); + fileManager.overwriteFile(FilePath.ORDERS_FILE_PATH, Encoder.encodeSales(sales)); + } + + //@@author ziyi105 + /** + * Encode and write the data from menu, orderList and pantry to the respective text files + * + * @param menu menu from current session + * @param sales sale object from current session + * @param pantry pantry from current session + */ + public void saveAll(Menu menu, Sales sales, Pantry pantry) { + try { + saveMenu(menu); + saveOrderList(sales); + savePantryStock(pantry); + } catch (IOException e) { + logger.log(Level.WARNING, "Saving unsuccessful!\n" + e.getMessage(), e); + ui.showToUser(e.getMessage()); + } + } + +} diff --git a/src/main/java/seedu/cafectrl/ui/ErrorMessages.java b/src/main/java/seedu/cafectrl/ui/ErrorMessages.java new file mode 100644 index 0000000000..fc51a24c35 --- /dev/null +++ b/src/main/java/seedu/cafectrl/ui/ErrorMessages.java @@ -0,0 +1,113 @@ +package seedu.cafectrl.ui; + +import seedu.cafectrl.command.EditPriceCommand; +import seedu.cafectrl.command.ListTotalSalesCommand; + +public class ErrorMessages { + public static final String INVALID_ADD_DISH_FORMAT = "Error: Incorrect format for the add command.\n"; + public static final String INVALID_INGREDIENT_QTY_FORMAT = "Ingredient Qty is in the wrong format, " + + "please use the format qty/ where VALUE is an integer"; + public static final String NULL_NAME_DETECTED_MESSAGE = "Error: Null dish name detected"; + public static final String REPEATED_DISH_MESSAGE = "Sorry, this dish name already exists."; + public static final String REPEATED_NAME_ARGUMENT = "Error: multiple 'name/' argument detected"; + public static final String REPEATED_PRICE_ARGUMENT = "Error: multiple 'price/' argument detected"; + public static final String REPEATED_INGREDIENT_ARGUMENT = "Error: multiple 'ingredient/' argument detected, " + + "perhaps you forgot to put a ',' somewhere"; + public static final String REPEATED_QTY_ARGUMENT = "Error: multiple 'qty/' argument detected, " + + "perhaps you forgot to put a ',' somewhere"; + public static final String MISSING_DISH_NAME = "Seems like you forgot to add the dish name"; + public static final String MISSING_INGREDIENT_NAME = "Seems like you forgot to add the ingredient name " + + "for the ingredient(s)"; + public static final String LARGE_PRICE_MESSAGE = "Wow! This dish must taste heavenly " + + "to cost that much money!\nBut it might be too expensive for normal people like me :(\n" + + "Maybe you should keep it below $1000000 instead?"; + public static final String NEGATIVE_PRICE_MESSAGE = "Negative price? We can't be paying " + + "the customers for eating in our cafe right, " + + "the minimum price acceptable is $0.00!"; + public static final String INVALID_DISH_NAME_LENGTH_MESSAGE = "Error: Your dish name length is too long!\n" + + "Please ensure your dish name is less than 35 characters."; + public static final String INVALID_INGREDIENT_NAME_LENGTH_MESSAGE = "Error: Your ingredient name length is " + + "too long!\nPlease ensure your ingredient name is less than 35 characters."; + public static final String MISSING_ARGUMENT_FOR_EDIT_PRICE = "Error: Missing arguments " + + "for edit price command.\n" + + EditPriceCommand.MESSAGE_USAGE; + public static final String MISSING_ARGUMENT_FOR_LIST_INGREDIENTS = "Error: Missing arguments " + + "for list ingredients command."; + public static final String MISSING_ARGUMENT_FOR_DELETE = "Error: Missing arguments " + + "for delete command."; + public static final String WRONG_DISH_INDEX_TYPE_FOR_EDIT_PRICE = "Something is wrong with " + + "the arguments! The types for dish and price are integer and float respectively, \n" + + "and do not type in duplicated arguments at one time!"; + public static final String WRONG_PRICE_TYPE_FOR_EDIT_PRICE = "Error: Invalid price!\n" + + "Price must be a float and within the range of 0.00 to 1000000.00 with up to 2 decimal place. \n" + + "Special characters such as $ are not allowed!"; + public static final String UNKNOWN_COMMAND_MESSAGE = "Error: Unknown command. " + + "Type 'help' to view the accepted list of commands"; + public static final String INVALID_DISH_INDEX = "Do we even have this dish? " + + "Double check the index of the dish you wanna modify!"; + public static final String INVALID_ADD_ORDER_FORMAT_MESSAGE = "Error: Incorrect format " + + "for the add order command."; + public static final String DATA_FOLDER_NOT_FOUND_MESSAGE = "Data Folder was not found!\nIt's ok... " + + "a new data folder has been created."; + public static final String DISH_NOT_FOUND = "I'm sorry, but it appears that dish is so exclusive " + + "it hasn't even made it to our menu yet!"; + public static final String ERROR_IN_PANTRY_STOCK_DATA = "pantry_stock.txt: Invalid format, " + + "this pantry stock will be removed -> "; + public static final String UNIT_NOT_MATCHING = ": Sorry, you have used a " + + "different unit for this ingredient!" + + "\nUnit used previously: "; + public static final String IGNORE_REMAINING_INGREDIENTS = "\nRemaining ingredients after this not added"; + public static final String RETYPE_COMMAND_MESSAGE = "\nPlease re-enter command with appropriate units."; + public static final String MENU_FILE_NOT_FOUND_MESSAGE = "Menu data was not found!\n" + + "No worries, new menu has been created"; + public static final String PANTRY_FILE_NOT_FOUND_MESSAGE = "Pantry stock data was not found!\n" + + "No worries, new pantry has been created"; + public static final String ORDER_LIST_FILE_NOT_FOUND_MESSAGE = "Order list data was not found!\n" + + "No worries, new order list has been created"; + public static final String INVALID_SHOW_SALE_DAY_FORMAT_MESSAGE = "Error: " + + "Incorrect format for the show_sale command.\n"; + public static final String INVALID_DAY_FORMAT = "Sorry, please enter a valid integer(>0)" + + "for the 'day' field!"; + public static final String EDIT_SAME_PRICE = "New price is exactly the same as old price, " + + "is that what you want?"; + public static final String INVALID_DISH_INDEX_TO_LIST = "Please enter a valid integer (>0) " + + "for the 'index' field."; + public static final String UNLISTED_DISH = "Oh no, this dish does not exist! \n" + + "Please run the command list_menu to view the existing dishes."; + public static final String INVALID_SALE_DAY = "Oh no, we have not reached the day entered!"; + public static final String EMPTY_UNIT_MESSAGE = "Unit cannot be empty! Please use either g or ml :)"; + public static final String INVALID_UNIT_MESSAGE = "Invalid unit! Please use either g or ml :)"; + public static final String INVALID_INGREDIENT_ARGUMENTS = "Invalid arguments for ingredients!\n" + + "Ingredient format: ingredient/INGREDIENT1_NAME qty/INGREDIENT1_QTY" + + "[, ingredient/INGREDIENT2_NAME, qty/INGREDIENT2_QTY...]\n" + + "Example: ingredient/milk qty/200ml, ingredient/chicken qty/100g"; + public static final String INVALID_INGREDIENT_QTY = "Quantity out of range! Quantity range is 1 to 1000000 :)"; + public static final String MISSING_PRICE = "Did you forget to include price? Just a reminder: " + + "price can only have up to 2 decimal place!"; + public static final String MISSING_DISH_IN_EDIT_PRICE = "Sorry, I didnt catch the dish index, " + + "did you forget to include it in your command?"; + public static final String INVALID_MENU_DATA = "menu.txt: Invalid format, this dish will be removed -> "; + public static final String NAME_CANNOT_CONTAIN_SPECIAL_CHAR = "Is there a special character in the name?\n" + + "I have poor memory and am unable to remember names with special characters, could you remove them?"; + public static final String REPEATED_INGREDIENT_NAME = "Error: there's a repeat in ingredient name " + + "for the add command input"; + public static final String NULL_STRING_IN_REPEAT_ARGUMENT = "Null string detected in isRepeatedArgument function"; + public static final String INVALID_SALES_DATA = "orders.txt: Invalid format, this order will be removed -> "; + public static final String INVALID_ORDER_STATUS = "orders.txt: Invalid status, this order will be removed -> "; + public static final String INVALID_ORDER_QTY = "orders.txt: Invalid quantity (order quantity has to be more than 0)" + + ", this order will be removed -> "; + public static final String INVALID_DISH_PRICE = "orders.txt: Invalid dish price, this order will be removed -> "; + public static final String WRONG_LIST_TOTAL_SALES_FORMAT = "Invalid list_total_sales command format!\n" + + ListTotalSalesCommand.MESSAGE_USAGE; + public static final String MISSING_FILEPATH = "Error in FileManager: No File Path entered"; + public static final String MISSING_INGREDIENT_MENU_DATA = "menu.txt: Missing ingredients, " + + "this dish will be removed -> "; + public static final String DISH_INDEX_NOT_INT = "Sorry! Dish index has to be an int!"; + public static final String EXCEED_MAX_ORDER_QTY = "Order quantity too high! " + + "(order quantity has to be between 1 - 10000)"; + public static final String BELOW_MIN_ORDER_QTY = "Order quantity cannot be less than 1! " + + "(order quantity has to be between 1 - 10000)"; + public static final String MISSING_ORDER_QTY = "Seems like you forgot to add the order quantity"; + public static final String INVALID_INT_ORDER_QTY = "Order quantity is invalid! " + + "(order quantity has to be between 1 - 10000)"; +} diff --git a/src/main/java/seedu/cafectrl/ui/Messages.java b/src/main/java/seedu/cafectrl/ui/Messages.java new file mode 100644 index 0000000000..b0f3152bd4 --- /dev/null +++ b/src/main/java/seedu/cafectrl/ui/Messages.java @@ -0,0 +1,108 @@ +package seedu.cafectrl.ui; + +public class Messages { + + /** Greeting messages */ + public static final String LINE_STRING = "------------------------------------------------------------------------"; + public static final String EQUAL_LINE_STRING = "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = " + + "= = = ="; + + public static final String GOODBYE_MESSAGE = "Goodbye <3 Have a great day ahead!"; + + public static final String LOGO = + " _/_/_/ _/_/ _/_/_/ _/ _/ \n" + + " _/ _/_/_/ _/ _/_/ _/ _/_/_/_/ _/ _/_/ _/ \n" + + " _/ _/ _/ _/_/_/_/ _/_/_/_/ _/ _/ _/_/ _/ \n" + + "_/ _/ _/ _/ _/ _/ _/ _/ _/ \n" + + " _/_/_/ _/_/_/ _/ _/_/_/ _/_/_/ _/_/ _/ _/ "; + + public static final String WELCOME_MESSAGE = "Hello! Welcome to \n" + LOGO; + + /** Messages for edit price command */ + public static final String PRICE_MODIFIED_MESSAGE = "Price modified for the following dish: "; + + /** Messages for list menu command */ + public static final String LIST_MENU_MESSAGE = "| Ah, behold, the grand menu of delights! |"; + public static final String MENU_EMPTY_MESSAGE = "It seems our menu is currently taking a break. " + + "Let's give it a wake-up call and fill 'er up with delectable delights, shall we?"; + public static final String MENU_END_CAP = "+-------------------------------------------------------+"; + public static final String MENU_CORNER = "+----------------------------------------+--------------+"; + public static final String MENU_TITLE = "| Dish Name | Price |"; + + /** Messages for add dish command */ + public static final String ADD_DISH_MESSAGE = "You have added the following dish..."; + + /** Messages for view stock command */ + public static final String EMPTY_STOCK = "Sorry! Pantry is currently empty!"; + public static final String VIEW_STOCK_MESSAGE = "| You have the following ingredients in pantry: |"; + public static final String VIEW_STOCK_TITLE_MESSAGE = "| Ingredients | Qty |"; + + /** Messages for help command */ + public static final String LIST_OF_COMMANDS = "These are all the commands I recognise: "; + public static final String INSTRUCTION_ON_COMMAND_FORMAT = "- Words in UPPER_CASE are " + + "the parameters to be supplied by the user.\n" + + " e.g. in add name/NAME, NAME is a parameter that can be used as add name/Chicken.\n" + + "- Parameters in [] are optional."; + + /** Messages for show_sales command */ + public static final String SHOW_SALES_DAY_PART_1 = "| Day "; + public static final String SHOW_SALES_DAY_PART_2 = ": " + + " |"; + public static final String SHOW_SALES_TITLE = "| Dish Name " + + "| Dish Qty | Total Cost Price |"; + public static final String SHOW_SALES_END_CAP = "+---------------------------------------" + + "------------------------------------+"; + public static final String SHOW_SALES_CORNER = "+----------------------------------------" + + "+--------------+-------------------+"; + + /** Messages for order command */ + public static final String CHEF_MESSAGE = "I'm busy crafting your selected dish " + + "in the virtual kitchen of your dreams. Bon appétit!"; + public static final String PREVIOUS_DAY_TIME_TRAVEL = "Whoa there, time traveler! " + + "Unfortunately, the DeLorean can't take us back to the previous day because it's already Day 1, " + + "and there's no rewind button in this space-time continuum!"; + public static final String PREVIOUS_DAY_COMMAND_MESSAGE = "Sure thing! " + + "Let's rev up the virtual DeLorean and take a spin to the previous day. " + + "Buckle up, it's time to hit that rewind button!"; + public static final String NEXT_DAY_COMMAND_MESSAGE = "Prepare for liftoff! " + + "We're about to fast-forward to the next day. " + + "Hold onto your hats; here we go!"; + + /** Messages for restocking ingredients */ + public static final String AVAILABLE_DISHES = "Listed below are the availability of the dishes for the next order!"; + public static final String COMPLETE_ORDER = "Order is ready!"; + public static final String INCOMPLETE_ORDER = "Please restock ingredients before preparing the order :) "; + public static final String RESTOCK_END_CAP = "+-----------------------------------------" + + "-----------------------------+"; + public static final String RESTOCK_CORNER = "+----------------------------------------+" + + "--------------+--------------+"; + public static final String RESTOCK_TITLE = "| Restock | " + + "Current | Needed |"; + + /** Messages for list ingredients */ + public static final String INGREDIENTS_END_CAP = "+-------------------------------------------------------+"; + public static final String INGREDIENTS_CORNER = "+----------------------------------------+--------------+"; + public static final String INGREDIENTS_TITLE = "| Ingredient + Quantity |"; + /** Messages for decoder **/ + public static final String SAVE_FILE_TAMPER_DETECTED = "Well, well, well, " + + "looks like someone's been playing with the save files!\n" + + "Let's keep it classy and use the prescribed format below.\n"; + + public static final String SAVE_FILE_FORMAT_MENU = "Format for Menu.txt: \n" + + "{Dish Name} | {Dish Price} | {Ingredient Name} - {Ingredient Qty} - {Ingredient Unit} |"; + public static final String SAVE_FILE_FORMAT_ORDERS = "Format for Orders.txt: \n" + + "{Order Day} | {Dish Name} | {Dish Order Qty} | {Dish Price} | {Order Complete Status}"; + public static final String SAVE_FILE_FORMAT_PANTRY_STOCK = "Format for Pantry_stock.txt: \n" + + "{Ingredient Name} | {Ingredient Qty} | {Ingredient Unit}"; + + public static final String HASH_STRING_TAMPERED = "Alert! It appears the hash string " + + "got a makeover without permission."; + public static final String HASH_STRING_MESSAGE = "Please resist the temptation to play Picasso with this string.\n"; + + public static final String DONE_LOADING_MENU = "Done loading menu.txt!"; + public static final String DONE_LOADING_PANTRY_STOCK = "Done loading pantry_stock.txt!"; + public static final String DONE_LOADING_SALES = "Done loading orders.txt!"; + + /** Messages for buy ingredients command */ + public static final String BUY_INGREDIENT_HEADER = "Added to stock:"; +} diff --git a/src/main/java/seedu/cafectrl/ui/Ui.java b/src/main/java/seedu/cafectrl/ui/Ui.java new file mode 100644 index 0000000000..e6145de608 --- /dev/null +++ b/src/main/java/seedu/cafectrl/ui/Ui.java @@ -0,0 +1,296 @@ +package seedu.cafectrl.ui; + +import seedu.cafectrl.command.AddDishCommand; +import seedu.cafectrl.command.AddOrderCommand; +import seedu.cafectrl.command.BuyIngredientCommand; +import seedu.cafectrl.command.DeleteDishCommand; +import seedu.cafectrl.command.EditPriceCommand; +import seedu.cafectrl.command.ExitCommand; +import seedu.cafectrl.command.HelpCommand; +import seedu.cafectrl.command.ListIngredientCommand; +import seedu.cafectrl.command.ListMenuCommand; +import seedu.cafectrl.command.ListSaleByDayCommand; +import seedu.cafectrl.command.ListTotalSalesCommand; +import seedu.cafectrl.command.NextDayCommand; +import seedu.cafectrl.command.PreviousDayCommand; +import seedu.cafectrl.command.ViewTotalStockCommand; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; + +import java.util.ArrayList; +import java.util.Scanner; + +public class Ui { + public static final int OFFSET_LIST_INDEX = 1; + public static final String DECIMAL_POINT_STRING = ". "; + public static final String DOLLAR_SIGH_STRING = " $"; + public static final String DISH_LEFT_ALIGN_FORMAT = "|%-55s|"; + public static final String USER_INPUT_CHARACTER = "> "; + private final Scanner scanner; + + /** + * Constructs a UI instance with a Scanner for user input. + */ + public Ui() { + scanner = new Scanner(System.in); + } + + public void printLine() { + showToUser(Messages.LINE_STRING); + } + + public String receiveUserInput() { + System.out.print(USER_INPUT_CHARACTER); + return scanner.nextLine(); + } + + public void showWelcome() { + showToUser(Messages.WELCOME_MESSAGE); + } + + public void showGoodbye() { + showToUser(Messages.GOODBYE_MESSAGE); + } + + //@@author NaychiMin + /** + * Prints out the quantity of each ingredient needed for the + * dish in a table format. + * + * @param dish Dish for ingredients to be listed out. + */ + public void showListIngredientsMessage(Dish dish) { + showDishNameHeader(dish); + showIngredientList(dish); + showIngredientsEndCap(); + } + + public void showDishNameHeader(Dish dish) { + String dishNameString = String.format(DISH_LEFT_ALIGN_FORMAT, " Dish: " + dish.getName()); + showToUser(Messages.INGREDIENTS_END_CAP, + dishNameString, + Messages.INGREDIENTS_CORNER); + } + + public void showIngredientList(Dish dish) { + showToUser(Messages.INGREDIENTS_TITLE, + Messages.INGREDIENTS_CORNER); + + ArrayList ingredients = dish.getIngredients(); + for (Ingredient ingredient : ingredients) { + formatListIngredient(ingredient.getName(), ingredient.getQty() + ingredient.getUnit()); + } + } + + public void showIngredientsEndCap() { + showToUser(Messages.INGREDIENTS_END_CAP); + } + + //@@author DextheChik3n + public void printAddDishMessage(Dish dish) { + showToUser(Messages.ADD_DISH_MESSAGE); + showDishNameHeader(dish); + showDishPrice(dish); + showIngredientList(dish); + showIngredientsEndCap(); + } + + public void showDishPrice(Dish dish) { + String dishPriceString = String.format(DISH_LEFT_ALIGN_FORMAT, " Price: $" + dish.getPriceString()); + showToUser(dishPriceString, Messages.INGREDIENTS_CORNER); + } + + //@@author ShaniceTang + /** + * Shows delete message to user + * + * @param selectedDish Dish to be deleted + */ + public void printDeleteMessage(Dish selectedDish) { + showToUser("Okay! " + selectedDish.getName() + " is deleted! :)"); + } + + public void printBuyIngredientHeader() { + showToUser(Messages.BUY_INGREDIENT_HEADER); + } + + /** + * Shows messages(s) to the user + * @param message string(s) of messages to print + */ + public void showToUser(String... message) { + for (String m: message) { + System.out.println(m); + } + } + + /** + * Shows menu to user is table format + * + * @param dishName name text of the dish + * @param dishPrice price text of the dish + */ + public void formatListMenu(String dishName, String dishPrice) { + String leftAlignFormat = "| %-38s | %-12s |%n"; + System.out.format(leftAlignFormat, dishName, dishPrice); + } + + /** + * show edit price message to user + * + * @param menuItem menuItem that has been modified + */ + public void showEditPriceMessage(String menuItem) { + this.showToUser(Messages.PRICE_MODIFIED_MESSAGE, menuItem); + } + + public void showHelp() { + showToUserWithSpaceBetweenLines(Messages.LIST_OF_COMMANDS, + Messages.INSTRUCTION_ON_COMMAND_FORMAT); + + showToUser(Messages.LINE_STRING); + ArrayList usagesTexts = new ArrayList<>(); + + usagesTexts.add(AddDishCommand.MESSAGE_USAGE); + usagesTexts.add(DeleteDishCommand.MESSAGE_USAGE); + usagesTexts.add(EditPriceCommand.MESSAGE_USAGE); + usagesTexts.add(ListMenuCommand.MESSAGE_USAGE); + usagesTexts.add(ListIngredientCommand.MESSAGE_USAGE); + usagesTexts.add(BuyIngredientCommand.MESSAGE_USAGE); + usagesTexts.add(ViewTotalStockCommand.MESSAGE_USAGE); + usagesTexts.add(AddOrderCommand.MESSAGE_USAGE); + usagesTexts.add(ListTotalSalesCommand.MESSAGE_USAGE); + usagesTexts.add(ListSaleByDayCommand.MESSAGE_USAGE); + usagesTexts.add(NextDayCommand.MESSAGE_USAGE); + usagesTexts.add(PreviousDayCommand.MESSAGE_USAGE); + usagesTexts.add(ExitCommand.MESSAGE_USAGE); + usagesTexts.add(HelpCommand.MESSAGE_USAGE); + + showToUserWithSpaceBetweenLines(usagesTexts); + } + + public void showToUserWithSpaceBetweenLines(String... message) { + for (String m: message) { + System.out.println(m + "\n"); + } + } + + public void showToUserWithSpaceBetweenLines(ArrayList message) { + for (String m: message) { + System.out.println(m); + System.out.println(Messages.LINE_STRING); + } + } + + public void showDishAvailability(int numberOfDishes){ + showToUser("Available quantity: " + numberOfDishes); + } + + public void showNeededRestock(String ingredientName, int currentQuantity, String unit, int neededIngredient) { + String rowFormat = "| %-38s | %-12s | %-12s |%n"; + System.out.format(rowFormat, ingredientName, currentQuantity + unit, neededIngredient + unit); + showToUser(Messages.RESTOCK_END_CAP); + } + + /** + * Shows the top portion of the menu + */ + public void showMenuTop() { + showToUser(Messages.MENU_END_CAP, Messages.LIST_MENU_MESSAGE, + Messages.MENU_CORNER, Messages.MENU_TITLE, Messages.MENU_CORNER); + } + + /** + * Shows the bottom portion of the menu + */ + public void showMenuBottom() { + showToUser(Messages.MENU_END_CAP); + } + + /** + * Shows the message for empty menu + */ + public void showEmptyMenu() { + showToUser(Messages.MENU_EMPTY_MESSAGE); + } + + /** + * Shows the dishes in the menu, formatted in the proper format + * + * @param indexNum The index number of the dish in the menu print + * @param dishName The name of the dish in the menu + * @param dishPrice The price of the dish in the menu + */ + public void showMenuDish(String indexNum, String dishName, String dishPrice) { + formatListMenu(indexNum + DECIMAL_POINT_STRING + dishName, DOLLAR_SIGH_STRING + dishPrice); + } + + public void showIngredientTop() { + showToUser(Messages.MENU_END_CAP, Messages.VIEW_STOCK_MESSAGE, + Messages.MENU_CORNER, Messages.VIEW_STOCK_TITLE_MESSAGE, Messages.MENU_CORNER); + } + + public void showIngredientStock(String ingredientName, int ingredientQty, String ingredientUnit) { + formatListIngredient(ingredientName, ingredientQty + ingredientUnit); + } + + public void formatListIngredient(String ingredientName, String ingredientAmount) { + String leftAlignFormat = "| %-38s | %-12s |%n"; + System.out.format(leftAlignFormat, ingredientName, ingredientAmount); + } + + public void showSalesTop(int day) { + showToUser(Messages.SHOW_SALES_END_CAP, Messages.SHOW_SALES_DAY_PART_1 + day + Messages.SHOW_SALES_DAY_PART_2, + Messages.SHOW_SALES_CORNER, Messages.SHOW_SALES_TITLE, Messages.SHOW_SALES_CORNER); + } + public void showSalesBottom() { + showToUser(Messages.SHOW_SALES_END_CAP); + } + public void showSalesAll(String dishName, int dishQty, String dishPrice) { + formatShowSales(dishName, dishQty, dishPrice); + } + + public void formatShowSales(String dishName, int dishQty, String dishPrice) { + String leftAlignFormat = "| %-38s | %-12s | %-17s |%n"; + System.out.format(leftAlignFormat, dishName, dishQty, dishPrice); + } + public void showSalesCost(String front, String back) { + String leftAlignFormat = "| %-53s | %-17s |%n"; + System.out.format(leftAlignFormat, front, back); + } + + public void showChefMessage() { + showToUser(Messages.CHEF_MESSAGE); + } + + /** + * Shows the total cost in the order list, formatted in the proper format + * + * @param dollarCost The price of the orders + */ + public void showTotalCost(String dollarCost) { + showToUser("Total order cost: $" + dollarCost); + } + + public void showOrderStatus(String totalCost) { + printLine(); + showToUser(Messages.COMPLETE_ORDER); + showTotalCost(totalCost); + printLine(); + showDishAvailabilityMessage(); + } + public void showIncompleteOrder() { + showToUser(Messages.INCOMPLETE_ORDER); + } + + public void showDishAvailabilityMessage() { + showToUser(Messages.AVAILABLE_DISHES, + Messages.EQUAL_LINE_STRING); + } + public void showPreviousDay() { + showToUser(Messages.PREVIOUS_DAY_COMMAND_MESSAGE); + } + public void showNextDay() { + showToUser(Messages.NEXT_DAY_COMMAND_MESSAGE); + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/test/java/seedu/cafectrl/command/AddDishCommandTest.java b/src/test/java/seedu/cafectrl/command/AddDishCommandTest.java new file mode 100644 index 0000000000..470fe88c35 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/AddDishCommandTest.java @@ -0,0 +1,29 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AddDishCommandTest { + @Test + void execute_oneDishAdded_expectDishInMenu() { + ArrayList ingredients = new ArrayList<>(); + ArrayList menuItems = new ArrayList<>(); + Menu menu = new Menu(menuItems); + //creating a dish + ingredients.add(new Ingredient("chicken", 100, "g")); + Dish dish = new Dish("Chicken Rice", ingredients, (float) 1.00); + Ui ui = new Ui(); + + AddDishCommand addDishCommand = new AddDishCommand(dish, menu, ui); + addDishCommand.execute(); + + assertEquals(1, menu.getMenuItemsList().size()); + } +} diff --git a/src/test/java/seedu/cafectrl/command/AddOrderCommandTest.java b/src/test/java/seedu/cafectrl/command/AddOrderCommandTest.java new file mode 100644 index 0000000000..e7d2148f42 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/AddOrderCommandTest.java @@ -0,0 +1,135 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AddOrderCommandTest { + @Test + public void execute_addValidOrder_expectOneOrder() { + ArrayList ingredients = new ArrayList<>(); + ingredients.add(new Ingredient("chicken", 100, "g")); + ingredients.add(new Ingredient("rice", 50, "g")); + + ArrayList ingredients2 = new ArrayList<>(); + ingredients2.add(new Ingredient("chicken", 200, "g")); + ingredients2.add(new Ingredient("rice", 50, "g")); + + ArrayList menuItems = new ArrayList<>(); + Dish dishChickenRice = new Dish("chicken rice", ingredients, 2.50F); + Dish dishChickenCurry = new Dish("chicken curry", ingredients2, 4.30F); + menuItems.add(dishChickenRice); + menuItems.add(dishChickenCurry); + Menu menu = new Menu(menuItems); + assertEquals(2, menu.getSize()); + + Order orderChickenRice = new Order(dishChickenRice, 2); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + ArrayList pantryStock = new ArrayList<>(); + pantryStock.add(new Ingredient("chicken", 1000, "g")); + pantryStock.add(new Ingredient("rice", 1000, "g")); + Pantry pantry = new Pantry(ui, pantryStock); + + OrderList orderList = new OrderList(); + + Command addOrderCommand = new AddOrderCommand(orderChickenRice, ui, pantry, orderList, menu); + addOrderCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = Messages.CHEF_MESSAGE + + Messages.LINE_STRING + + Messages.COMPLETE_ORDER + + "Total order cost: $5.00" + + Messages.LINE_STRING + + Messages.AVAILABLE_DISHES + + Messages.EQUAL_LINE_STRING + + "Dish: chicken rice" + + "Available quantity: 8" + + Messages.LINE_STRING + + "Dish: chicken curry" + + "Available quantity: 4"; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + + //one more test addInvalidOrder_expecterror + @Test + public void execute_addInvalidOrder_expectRestockMessage() { + ArrayList ingredients = new ArrayList<>(); + ingredients.add(new Ingredient("chicken", 200, "g")); + ingredients.add(new Ingredient("rice", 100, "g")); + + ArrayList ingredients2 = new ArrayList<>(); + ingredients2.add(new Ingredient("chicken", 200, "g")); + ingredients2.add(new Ingredient("rice", 50, "g")); + + ArrayList menuItems = new ArrayList<>(); + Dish dishChickenRice = new Dish("Chicken Rice", ingredients, 2.50F); + Dish dishChickenCurry = new Dish("Chicken Curry", ingredients2, 4.30F); + menuItems.add(dishChickenRice); + menuItems.add(dishChickenCurry); + Menu menu = new Menu(menuItems); + assertEquals(2, menu.getSize()); + + Order orderChickenRice = new Order(dishChickenRice, 10); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + ArrayList pantryStock = new ArrayList<>(); + pantryStock.add(new Ingredient("chicken", 1000, "g")); + pantryStock.add(new Ingredient("rice", 1000, "g")); + Pantry pantry = new Pantry(ui, pantryStock); + + OrderList orderList = new OrderList(); + + Command addOrderCommand = new AddOrderCommand(orderChickenRice, ui, pantry, orderList, menu); + addOrderCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = Messages.CHEF_MESSAGE + + Messages.RESTOCK_CORNER + + Messages.RESTOCK_TITLE + + Messages.RESTOCK_CORNER + + "| chicken | 1000g | 2000g |" + + Messages.RESTOCK_END_CAP + + Messages.INCOMPLETE_ORDER; + + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + +} diff --git a/src/test/java/seedu/cafectrl/command/BuyIngredientCommandTest.java b/src/test/java/seedu/cafectrl/command/BuyIngredientCommandTest.java new file mode 100644 index 0000000000..d6c3295efc --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/BuyIngredientCommandTest.java @@ -0,0 +1,73 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BuyIngredientCommandTest { + + @Test + void execute_validInput_returnCorrectOutput() { + ArrayList ingredientsList = new ArrayList<>(); + ingredientsList.add(new Ingredient("chicken", 500, "g")); + ingredientsList.add(new Ingredient("rice", 1000, "g")); + ingredientsList.add(new Ingredient("milk", 100, "ml")); + + Ui ui = new Ui(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + Pantry pantry = new Pantry(ui); + BuyIngredientCommand buyIngredientCommand = new BuyIngredientCommand(ingredientsList, ui, pantry); + + buyIngredientCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = "Added to stock: \n" + + "Ingredient: milk\nTotal Qty: 100ml\n\n" + + "Ingredient: rice\nTotal Qty: 1000g\n\n" + + "Ingredient: chicken\nTotal Qty: 500g\n\n"; + + assertEquals(expectedOutput.trim().replaceAll("\\s+", " "), + actualOutput.trim().replaceAll("\\s+", " ")); + } + + @Test + void execute_validInputWithExistingIngredient_returnCorrectOutput() { + ArrayList ingredientsList = new ArrayList<>(); + ingredientsList.add(new Ingredient("chicken", 500, "g")); + + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui, ingredientsList); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + BuyIngredientCommand buyIngredientCommand = new BuyIngredientCommand(ingredientsList, ui, pantry); + + buyIngredientCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = "Added to stock: \n" + + "Ingredient: chicken\nTotal Qty: 1000g\n"; + + assertEquals(expectedOutput.trim().replaceAll("\\s+", " "), + actualOutput.trim().replaceAll("\\s+", " ")); + } +} diff --git a/src/test/java/seedu/cafectrl/command/CommandTest.java b/src/test/java/seedu/cafectrl/command/CommandTest.java new file mode 100644 index 0000000000..0c92114477 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/CommandTest.java @@ -0,0 +1,5 @@ +package seedu.cafectrl.command; + +class CommandTest { + +} diff --git a/src/test/java/seedu/cafectrl/command/DeleteDishCommandTest.java b/src/test/java/seedu/cafectrl/command/DeleteDishCommandTest.java new file mode 100644 index 0000000000..72d6234f1a --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/DeleteDishCommandTest.java @@ -0,0 +1,53 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class DeleteDishCommandTest { + + @Test + void execute_validInput() { + Menu menu = new Menu(); + menu.addDish(new Dish("Chicken Rice", 2.50F)); + menu.addDish(new Dish("Chicken Curry", 4.30F)); + menu.addDish(new Dish("Nasi Lemak", 5.60F)); + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + + int testIndex = 2; + DeleteDishCommand deleteDishCommand = new DeleteDishCommand(testIndex, menu, ui); + deleteDishCommand.execute(); + + int actualOutputIndex = 0; + String expectedOutput = "Okay! Chicken Curry is deleted! :)"; + assertEquals(expectedOutput, actualOutput.get(actualOutputIndex)); + } + + @Test + void execute_invalidInput_throwIndexOutOfBoundsException() { + Menu menu = new Menu(); + menu.addDish(new Dish("Chicken Rice", 2.50F)); + menu.addDish(new Dish("Chicken Curry", 4.30F)); + menu.addDish(new Dish("Nasi Lemak", 5.60F)); + + Ui ui = new Ui(); + int testIndex = 5; + DeleteDishCommand deleteDishCommand = new DeleteDishCommand(testIndex, menu, ui); + + assertThrows(IndexOutOfBoundsException.class, () -> deleteDishCommand.execute()); + } +} diff --git a/src/test/java/seedu/cafectrl/command/EditPriceCommandTest.java b/src/test/java/seedu/cafectrl/command/EditPriceCommandTest.java new file mode 100644 index 0000000000..57f88ab308 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/EditPriceCommandTest.java @@ -0,0 +1,42 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EditPriceCommandTest { + + @Test + void execute_validInput_editPrice() { + Menu menu = new Menu(); + Dish testDish = new Dish("Chicken Rice", 2.50F); + menu.addDish(testDish); + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + + int testDishIndex = 1; + float testNewPrice = 3; + Command editPriceCommand = new EditPriceCommand(testDishIndex, testNewPrice, menu, ui); + editPriceCommand.execute(); + + int firstLine = 0; + int secondLine = 1; + String expectedOutputFirstLine = Messages.PRICE_MODIFIED_MESSAGE; + String expectedOutputSecondLine = testDish.toString(); + assertEquals(expectedOutputFirstLine, actualOutput.get(firstLine)); + assertEquals(expectedOutputSecondLine, actualOutput.get(secondLine)); + } +} diff --git a/src/test/java/seedu/cafectrl/command/ListIngredientCommandTest.java b/src/test/java/seedu/cafectrl/command/ListIngredientCommandTest.java new file mode 100644 index 0000000000..da06b4426c --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/ListIngredientCommandTest.java @@ -0,0 +1,79 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ListIngredientCommandTest { + @Test + public void execute_validIndex_printsIngredients() { + ArrayList menuItems = new ArrayList<>(); + menuItems.add(new Dish("Chicken Rice", + new ArrayList<>(Arrays.asList(new Ingredient("Rice", 100, "g"), + new Ingredient("Chicken", 200, "g"))), 8.0F)); + menuItems.add(new Dish("Chicken Sandwich", + new ArrayList<>(Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))), 5.0F)); + Menu menu = new Menu(menuItems); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + int indexToSelect = 1; + ListIngredientCommand listIngredientCommand = new ListIngredientCommand(indexToSelect, menu, ui); + listIngredientCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = Messages.INGREDIENTS_END_CAP + + "| Dish: chicken rice |" + + Messages.INGREDIENTS_CORNER + + Messages.INGREDIENTS_TITLE + + Messages.INGREDIENTS_CORNER + + "| rice | 100g |" + + "| chicken | 200g |" + + Messages.INGREDIENTS_END_CAP; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + + } + + @Test + public void execute_invalidIndex_returnsErrorMessage() { + ArrayList menuItems = new ArrayList<>(); + menuItems.add(new Dish("Chicken Rice", + new ArrayList<>(Arrays.asList(new Ingredient("Rice", 1, "cup"), + new Ingredient("Chicken", 100, "g"))), 8.0F)); + menuItems.add(new Dish("Chicken Sandwich", + new ArrayList<>(Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))), 5.0F)); + Menu menu = new Menu(menuItems); + Ui ui = new Ui(); + int invalidIndex = 3; + + assertThrows(IllegalArgumentException.class, () -> { + ListIngredientCommand listIngredientCommand = new ListIngredientCommand(invalidIndex, menu, ui); + listIngredientCommand.execute(); + }); + } + +} diff --git a/src/test/java/seedu/cafectrl/command/ListMenuCommandTest.java b/src/test/java/seedu/cafectrl/command/ListMenuCommandTest.java new file mode 100644 index 0000000000..d22adcac97 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/ListMenuCommandTest.java @@ -0,0 +1,86 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ListMenuCommandTest { + @Test + public void execute_addTwoDishes_expectTwoDishes() { + ArrayList menuItems = new ArrayList<>(); + menuItems.add(new Dish("Chicken Rice", 2.50F)); + menuItems.add(new Dish("Chicken Curry", 4.30F)); + Menu menu = new Menu(menuItems); + assertEquals(2, menu.getSize()); + + ArrayList commandOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + String parseString = convertArrayToString(message, ","); + commandOutput.add(parseString); + } + @Override + public void formatListMenu(String dishName, String dishPrice) { + String leftAlignFormat = "| %-24s | %-12s |"; + String parseString = String.format(leftAlignFormat, dishName, dishPrice); + commandOutput.add(parseString); + } + }; + + Command listMenuCommand = new ListMenuCommand(menu, ui); + listMenuCommand.execute(); + + String actualOutput = String.join(",", commandOutput); + + String expectedOutput = Messages.MENU_END_CAP + + Messages.LIST_MENU_MESSAGE + + Messages.MENU_CORNER + + Messages.MENU_TITLE + + Messages.MENU_CORNER + + "| 1. Chicken Rice | $2.50 |" + + "| 2. Chicken Curry | $4.30 |" + + Messages.MENU_END_CAP; + + assert (expectedOutput.trim().replaceAll(",", "").equals(actualOutput.trim().replaceAll(",", ""))); + } + + @Test + public void execute_emptyMenu_expectEmptyMenuMessage() { + ArrayList menuItems = new ArrayList<>(); + Menu menu = new Menu(menuItems); + assertEquals(0, menu.getSize()); + ArrayList commandOutput = new ArrayList<>(); + + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + String parseString = convertArrayToString(message, ","); + commandOutput.add(parseString); + } + }; + + Command listMenuCommand = new ListMenuCommand(menu, ui); + listMenuCommand.execute(); + + String actualOutput = String.join(",", commandOutput); + + String expectedOutput = Messages.MENU_EMPTY_MESSAGE; + + assert (expectedOutput.trim().replaceAll(",", "").equals(actualOutput.trim().replaceAll(",", ""))); + } + + private static String convertArrayToString(String[] message, String delimiter) { + StringBuilder sb = new StringBuilder(); + for (String str : message) { + sb.append(str.toString()).append(delimiter); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/src/test/java/seedu/cafectrl/command/ListSaleByDayCommandTest.java b/src/test/java/seedu/cafectrl/command/ListSaleByDayCommandTest.java new file mode 100644 index 0000000000..9d1262d4f7 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/ListSaleByDayCommandTest.java @@ -0,0 +1,200 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListSaleByDayCommandTest { + + @Test + public void execute_validDayIndex_listSaleOfDay() { + // Create a dummy menu + ArrayList ingredients = new ArrayList<>( + Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredients, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredients, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + // Create a dummy order for day 1 + Order order1 = new Order(dishChickenRice, 2); + order1.setComplete(true); + Order order2 = new Order(dishChickenChop, 1); + order2.setComplete(true); + + // Create a dummy order list for day 1 + OrderList orderList1 = new OrderList(); + orderList1.addOrder(order1); + orderList1.addOrder(order2); + + // Create a dummy order for day 2 + Order order3 = new Order(dishChickenRice, 4); + order3.setComplete(false); + Order order4 = new Order(dishChickenChop, 1); + order4.setComplete(true); + Order order5 = new Order(dishChickenChop, 1); + order5.setComplete(true); + + // Create a dummy order list for day 2 + OrderList orderList2 = new OrderList(); + orderList2.addOrder(order3); + + ArrayList orderLists= new ArrayList<>(Arrays.asList(orderList1, orderList2)); + // Create a sales object and add the order lists + Sales sales = new Sales(orderLists); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + int dayToListSale = 1; + ListSaleByDayCommand listSaleByDayCommand = + new ListSaleByDayCommand(dayToListSale, ui, sales); + listSaleByDayCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = Messages.SHOW_SALES_END_CAP + + Messages.SHOW_SALES_DAY_PART_1 + + dayToListSale + + Messages.SHOW_SALES_DAY_PART_2 + + Messages.SHOW_SALES_CORNER + + Messages.SHOW_SALES_TITLE + + Messages.SHOW_SALES_CORNER + + "| chicken rice | 2 | 5.00 |" + + "| chicken chop | 1 | 5.00 |" + + Messages.SHOW_SALES_END_CAP + + "| Total for day: | $10.00 |" + + Messages.SHOW_SALES_END_CAP; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + + @Test + public void execute_invalidDayIndex_errorMessage() { + // Create a dummy menu + ArrayList ingredients = new ArrayList<>(Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredients, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredients, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + // Create a dummy order for day 1 + Order order1 = new Order(dishChickenRice, 2); + order1.setComplete(true); + Order order2 = new Order(dishChickenChop, 1); + order2.setComplete(true); + + // Create a dummy order list for day 1 + OrderList orderList1 = new OrderList(); + orderList1.addOrder(order1); + orderList1.addOrder(order2); + + ArrayList orderLists= new ArrayList<>(Arrays.asList(orderList1)); + // Create a sales object and add the order lists + Sales sales = new Sales(orderLists); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + int dayToListSale = 200; + ListSaleByDayCommand listSaleByDayCommand = + new ListSaleByDayCommand(dayToListSale, ui, sales); + listSaleByDayCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = ErrorMessages.INVALID_SALE_DAY;; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + + @Test + public void execute_validDayNoSales_showNoSalesMessage() { + // Create a dummy menu + ArrayList ingredients = new ArrayList<>(Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredients, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredients, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + // Create a dummy order for day 1 + Order order1 = new Order(dishChickenRice, 2); + order1.setComplete(true); + Order order2 = new Order(dishChickenChop, 1); + order2.setComplete(true); + + // Create a dummy order list for day 1 + OrderList orderList1 = new OrderList(); + orderList1.addOrder(order1); + orderList1.addOrder(order2); + + OrderList orderList2 = new OrderList(); + + OrderList orderList3 = new OrderList(); + orderList1.addOrder(order1); + + ArrayList orderLists= new ArrayList<>(Arrays.asList(orderList1, orderList2, orderList3)); + Sales sales = new Sales(orderLists); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + int dayToListSale = 2; + ListSaleByDayCommand listSaleByDayCommand = + new ListSaleByDayCommand(dayToListSale, ui, sales); + listSaleByDayCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = "No sales for this day."; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } +} diff --git a/src/test/java/seedu/cafectrl/command/ListTotalSalesCommandCommandTest.java b/src/test/java/seedu/cafectrl/command/ListTotalSalesCommandCommandTest.java new file mode 100644 index 0000000000..50ac260144 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/ListTotalSalesCommandCommandTest.java @@ -0,0 +1,143 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Order; +import seedu.cafectrl.data.OrderList; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListTotalSalesCommandCommandTest { + + @Test + public void execute_existingSales_listTotalSales() { + // Create a dummy menu + ArrayList ingredients = new ArrayList<>( + Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredients, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredients, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + // Create a dummy order for day 1 + Order order1 = new Order(dishChickenRice, 2); + order1.setComplete(true); + Order order2 = new Order(dishChickenChop, 1); + order2.setComplete(true); + + // Create a dummy order list for day 1 + OrderList orderList1 = new OrderList(); + orderList1.addOrder(order1); + orderList1.addOrder(order2); + + // Create a dummy order list for day 2 + OrderList orderList2 = new OrderList(); + + // Create a dummy order for day 3 + Order order3 = new Order(dishChickenRice, 4); + order3.setComplete(false); + Order order4 = new Order(dishChickenChop, 1); + order4.setComplete(true); + Order order5 = new Order(dishChickenChop, 1); + order5.setComplete(true); + + // Create a dummy order list for day 3 + OrderList orderList3 = new OrderList(); + orderList3.addOrder(order3); + orderList3.addOrder(order4); + orderList3.addOrder(order5); + + ArrayList orderLists= new ArrayList<>(Arrays.asList(orderList1, orderList2, orderList3)); + // Create a sales object and add the order lists + Sales sales = new Sales(orderLists); + + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + ListTotalSalesCommand listTotalSalesCommand = + new ListTotalSalesCommand(sales, ui); + listTotalSalesCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = Messages.SHOW_SALES_END_CAP + + Messages.SHOW_SALES_DAY_PART_1 + + "1" + + Messages.SHOW_SALES_DAY_PART_2 + + Messages.SHOW_SALES_CORNER + + Messages.SHOW_SALES_TITLE + + Messages.SHOW_SALES_CORNER + + "| chicken rice | 2 | 5.00 |" + + "| chicken chop | 1 | 5.00 |" + + Messages.SHOW_SALES_END_CAP + + "| Total for day: | $10.00 |" + + Messages.SHOW_SALES_END_CAP + + "No sales for day 2." + + Messages.SHOW_SALES_END_CAP + + Messages.SHOW_SALES_DAY_PART_1 + + "3" + + Messages.SHOW_SALES_DAY_PART_2 + + Messages.SHOW_SALES_CORNER + + Messages.SHOW_SALES_TITLE + + Messages.SHOW_SALES_CORNER + + "| chicken chop | 2 | 10.00 |" + + Messages.SHOW_SALES_END_CAP + + "| Total for day: | $10.00 |" + + Messages.SHOW_SALES_END_CAP; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + + @Test + public void execute_noSales_showNoSalesMessage() { + + Menu menu = new Menu(); + OrderList orderList1 = new OrderList(); + ArrayList orderLists= new ArrayList<>(List.of(orderList1)); + Sales sales = new Sales(orderLists); + + Ui ui = new Ui(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + ListTotalSalesCommand listTotalSalesCommand = + new ListTotalSalesCommand(sales, ui); + listTotalSalesCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = "No sales made."; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } +} diff --git a/src/test/java/seedu/cafectrl/command/NextDayCommandTest.java b/src/test/java/seedu/cafectrl/command/NextDayCommandTest.java new file mode 100644 index 0000000000..c5053cd256 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/NextDayCommandTest.java @@ -0,0 +1,34 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.ui.Ui; + +class NextDayCommandTest { + @Test + public void execute_oneNextDay_expectDayTwo() { + Ui ui = new Ui(); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(sales); + Command nextDayCommand = new NextDayCommand(ui, sales, currentDate); + nextDayCommand.execute(); + int actualDay = currentDate.getCurrentDay() + 1; + int expectedDay = 2; + assert actualDay == expectedDay; + } + + @Test + public void execute_nineNextDay_expectDayTen() { + Ui ui = new Ui(); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(sales); + for (int i = 0; i < 9; i ++) { + Command nextDayCommand = new NextDayCommand(ui, sales, currentDate); + nextDayCommand.execute(); + } + int actualDay = currentDate.getCurrentDay() + 1; + int expectedDay = 10; + assert actualDay == expectedDay; + } +} diff --git a/src/test/java/seedu/cafectrl/command/PreviousDayCommandTest.java b/src/test/java/seedu/cafectrl/command/PreviousDayCommandTest.java new file mode 100644 index 0000000000..9e24767e5a --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/PreviousDayCommandTest.java @@ -0,0 +1,31 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.ui.Ui; + +class PreviousDayCommandTest { + @Test + public void execute_setTwoDayOneRun_expectDayOne() { + Ui ui = new Ui(); + CurrentDate currentDate = new CurrentDate(2); + Command previousDayCommand = new PreviousDayCommand(ui, currentDate); + previousDayCommand.execute(); + int actualDay = currentDate.getCurrentDay() + 1; + int expectedDay = 1; + assert actualDay == expectedDay; + } + + @Test + public void execute_setTenDayNineRun_expectDayOne() { + Ui ui = new Ui(); + CurrentDate currentDate = new CurrentDate(10); + for (int i = 0; i < 9; i++) { + Command previousDayCommand = new PreviousDayCommand(ui, currentDate); + previousDayCommand.execute(); + } + int actualDay = currentDate.getCurrentDay() + 1; + int expectedDay = 1; + assert actualDay == expectedDay; + } +} diff --git a/src/test/java/seedu/cafectrl/command/ViewTotalStockCommandTest.java b/src/test/java/seedu/cafectrl/command/ViewTotalStockCommandTest.java new file mode 100644 index 0000000000..5992b75912 --- /dev/null +++ b/src/test/java/seedu/cafectrl/command/ViewTotalStockCommandTest.java @@ -0,0 +1,48 @@ +package seedu.cafectrl.command; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ViewTotalStockCommandTest { + @Test + void execute_printPantryStock() { + ArrayList pantryStock = new ArrayList<>(); + pantryStock.add(new Ingredient("chicken", 500, "g")); + pantryStock.add(new Ingredient("rice", 1000, "g")); + + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui, pantryStock); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + ViewTotalStockCommand viewTotalStockCommand = new ViewTotalStockCommand(pantry, ui); + viewTotalStockCommand.execute(); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = Messages.MENU_END_CAP + + Messages.VIEW_STOCK_MESSAGE + + Messages.MENU_CORNER + + Messages.VIEW_STOCK_TITLE_MESSAGE + + Messages.MENU_CORNER + + "| chicken | 500g |\n" + + "| rice | 1000g |\n" + + Messages.MENU_END_CAP; + + assertEquals(expectedOutput.trim().replaceAll("\\s+", ""), + actualOutput.trim().replaceAll("\\s+", "")); + } +} diff --git a/src/test/java/seedu/cafectrl/data/PantryTest.java b/src/test/java/seedu/cafectrl/data/PantryTest.java new file mode 100644 index 0000000000..9294f7068d --- /dev/null +++ b/src/test/java/seedu/cafectrl/data/PantryTest.java @@ -0,0 +1,272 @@ +package seedu.cafectrl.data; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Messages; +import seedu.cafectrl.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PantryTest { + @Test + void addIngredientToStock_differentUnitForBuyIngredient_returnErrorMessage() { + ArrayList ingredientsList = new ArrayList<>(); + ingredientsList.add(new Ingredient("chicken", 500, "g")); + + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui, ingredientsList); + + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + pantry.addIngredientToStock("chicken", 500, "ml"); + }); + + String expectedErrorMessage = "chicken" + ErrorMessages.UNIT_NOT_MATCHING + + "g" + ErrorMessages.IGNORE_REMAINING_INGREDIENTS; + assertEquals(expectedErrorMessage, exception.getMessage()); + } + + @Test + void isDishCooked_dishCooked_returnsTrue() { + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + + //the following models an order of two chicken rice + //each chicken rice needs 100g of chicken and 50g of rice + //dish ingredients contains the total quantity of ingredients needed for the order + ArrayList dishIngredients = new ArrayList<>(); + dishIngredients.add(new Ingredient("Chicken", 200, "g")); + dishIngredients.add(new Ingredient("Rice", 100, "g")); + + //pantryStock is the quantity of ingredients in the pantry + pantry.addIngredientToStock("Chicken", 300, "g"); + pantry.addIngredientToStock("Rice", 200, "g"); + + boolean expectedIsDishCooked = true; + boolean actualIsDishCooked = pantry.isDishCooked(dishIngredients); + + assert pantry.getPantryStock().get(0).getQty() == 100; + assert pantry.getPantryStock().get(1).getQty() == 100; + assertEquals(expectedIsDishCooked, actualIsDishCooked); + } + + @Test + void isDishCooked_dishNotCooked_returnsFalse() { + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + + //the following models an order of two chicken rice + //each chicken rice needs 100g of chicken and 50g of rice + //dish ingredients contains the total quantity of ingredients needed for the order + ArrayList dishIngredients = new ArrayList<>(); + dishIngredients.add(new Ingredient("Chicken", 200, "g")); + dishIngredients.add(new Ingredient("Rice", 100, "g")); + + //pantryStock is the quantity of ingredients in the pantry + pantry.addIngredientToStock("Chicken", 100, "g"); + pantry.addIngredientToStock("Rice", 200, "g"); + + boolean expectedIsDishCooked = false; + boolean actualIsDishCooked = pantry.isDishCooked(dishIngredients); + + assert pantry.getPantryStock().get(0).getQty() == 100; + assert pantry.getPantryStock().get(1).getQty() == 200; + assertEquals(expectedIsDishCooked, actualIsDishCooked); + } + + @Test + void calculateDishAvailability_orderCompleteNoRestock_showMaxDish() { + // Create a dummy menu + ArrayList ingredients = new ArrayList<>(Arrays.asList(new Ingredient("Rice", 100, "g"), + new Ingredient("Chicken", 50, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredients, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredients, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + Order order = new Order(dishChickenRice, 2); + order.setComplete(true); + + Ui ui = new Ui(); + + Pantry pantry = new Pantry(ui); + pantry.addIngredientToStock("Rice", 200, "g"); + pantry.addIngredientToStock("Chicken", 200, "g"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + pantry.calculateDishAvailability(menu, order); + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = "Dish: Chicken Rice" + + "Available quantity: 2" + + Messages.LINE_STRING + + "Dish: Chicken Chop" + + "Available quantity: 2" ; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + + @Test + void calculateMaxDish_orderCompleteNoRestock_showMaxDish() { + // Create a dummy menu + ArrayList ingredientChickenRice = new ArrayList<>(Arrays.asList(new Ingredient("Rice", 100, "g"), + new Ingredient("Chicken", 50, "g"))); + ArrayList ingredientChickenChop = new ArrayList<>(List.of(new Ingredient("Chicken", 150, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredientChickenRice, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredientChickenChop, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + Order order = new Order(dishChickenRice, 2); + order.setComplete(true); + + Ui ui = new Ui(); + + Pantry pantry = new Pantry(ui); + pantry.addIngredientToStock("Rice", 200, "g"); + pantry.addIngredientToStock("Chicken", 200, "g"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + //max number of chicken rice + assert pantry.calculateMaxDishes(dishChickenRice, menu, order) == 2; + //max number of chicken chop + assert pantry.calculateMaxDishes(dishChickenChop, menu, order) == 1; + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + String expectedOutput = ""; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + //No restock strings should be printed out + assertEquals(normalizedExpected, normalizedActual); + } + + @Test + void calculateMaxDish_orderCompleteRestock_showMaxDish() { + //This test case is for when the order is completed and there are not enough ingredients for the next + // Create a dummy menu + ArrayList ingredientChickenRice = new ArrayList<>(Arrays.asList(new Ingredient("Rice", 200, "g"), + new Ingredient("Chicken", 200, "g"))); + ArrayList ingredientChickenChop = new ArrayList<>(List.of(new Ingredient("Chicken", 200, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredientChickenRice, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredientChickenChop, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + Order order = new Order(dishChickenRice, 2); + order.setComplete(true); + + Ui ui = new Ui(); + + Pantry pantry = new Pantry(ui); + pantry.addIngredientToStock("Rice", 0, "g"); + pantry.addIngredientToStock("Chicken", 0, "g"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + //max number of chicken rice + assert pantry.calculateMaxDishes(dishChickenRice, menu, order) == 0; + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + //when dishChickenRice is passed in as the dish parameter + String expectedOutput = Messages.RESTOCK_CORNER + + Messages.RESTOCK_TITLE + + Messages.RESTOCK_CORNER + + "| Rice | 0g | 200g |" + + Messages.RESTOCK_END_CAP + + "| Chicken | 0g | 200g |" + + Messages.RESTOCK_END_CAP; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } + + @Test + void calculateMaxDish_orderIncompleteRestock_showMaxDish() { + // Create a dummy menu + ArrayList ingredientChickenRice = new ArrayList<>(Arrays.asList(new Ingredient("Rice", 200, "g"), + new Ingredient("Chicken", 200, "g"))); + ArrayList ingredientChickenChop = new ArrayList<>(List.of(new Ingredient("Chicken", 200, "g"))); + + Dish dishChickenRice = new Dish("Chicken Rice", ingredientChickenRice, 2.50f); + Dish dishChickenChop = new Dish("Chicken Chop", ingredientChickenChop, 5.00f); + Menu menu = new Menu(); + menu.addDish(dishChickenRice); + menu.addDish(dishChickenChop); + + Order order = new Order(dishChickenRice, 2); + order.setComplete(false); + + Ui ui = new Ui(); + + Pantry pantry = new Pantry(ui); + pantry.addIngredientToStock("Rice", 0, "g"); + pantry.addIngredientToStock("Chicken", 0, "g"); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream consoleStream = new PrintStream(baos); + + PrintStream originalOut = System.out; + System.setOut(consoleStream); + + //max number of chicken rice + assert pantry.calculateMaxDishes(dishChickenRice, menu, order) == 0; + + String actualOutput = baos.toString().trim(); + System.setOut(originalOut); + + //when dishChickenRice is passed in as the dish parameter + String expectedOutput = Messages.RESTOCK_CORNER + + Messages.RESTOCK_TITLE + + Messages.RESTOCK_CORNER + + "| Rice | 0g | 400g |" + + Messages.RESTOCK_END_CAP + + "| Chicken | 0g | 400g |" + + Messages.RESTOCK_END_CAP; + + String normalizedExpected = expectedOutput.toLowerCase().replaceAll("\\s+", "").trim(); + String normalizedActual = actualOutput.toLowerCase().replaceAll("\\s+", "").trim(); + + assertEquals(normalizedExpected, normalizedActual); + } +} diff --git a/src/test/java/seedu/cafectrl/parser/ParserTest.java b/src/test/java/seedu/cafectrl/parser/ParserTest.java new file mode 100644 index 0000000000..15b8f349bb --- /dev/null +++ b/src/test/java/seedu/cafectrl/parser/ParserTest.java @@ -0,0 +1,1124 @@ +package seedu.cafectrl.parser; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.command.AddDishCommand; +import seedu.cafectrl.command.DeleteDishCommand; +import seedu.cafectrl.command.ListIngredientCommand; +import seedu.cafectrl.command.ListSaleByDayCommand; +import seedu.cafectrl.command.ListTotalSalesCommand; +import seedu.cafectrl.command.Command; +import seedu.cafectrl.command.IncorrectCommand; +import seedu.cafectrl.command.ViewTotalStockCommand; + +import seedu.cafectrl.data.CurrentDate; +import seedu.cafectrl.data.Menu; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.Sales; +import seedu.cafectrl.data.dish.Dish; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.parser.exception.ParserException; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Junit test for Parser.java + */ +class ParserTest { + @Test + public void parseCommand_validListIngredientsCommand_successfulCommandParse() { + ArrayList menuItems = new ArrayList<>(); + menuItems.add(new Dish("Chicken Rice", + new ArrayList<>(Arrays.asList(new Ingredient("Rice", 50, "g"), + new Ingredient("Chicken", 100, "g"))), 8.0F)); + menuItems.add(new Dish("Chicken Sandwich", + new ArrayList<>(Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))), 5.0F)); + Menu menu = new Menu(menuItems); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String userInput = "list_ingredients dish/1"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof ListIngredientCommand); + + ListIngredientCommand listIngredientCommand = (ListIngredientCommand) result; + int index = listIngredientCommand.index; + assertEquals(1, index); + } + + @Test + public void parseCommand_missingListIngredientsIndex_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_ingredients"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.MISSING_ARGUMENT_FOR_LIST_INGREDIENTS + ListIngredientCommand.MESSAGE_USAGE, + feedbackToUser); + } + + @Test + public void parseCommand_invalidListIngredientsIndex_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_ingredients dish/a"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_DISH_INDEX_TO_LIST, feedbackToUser); + } + + @Test + public void parseCommand_listIngredientIndexOutOfBounds_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_ingredients dish/1"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.UNLISTED_DISH, feedbackToUser); + } + + @Test + public void parseCommand_validDeleteCommand_successfulCommandParse() { + ArrayList menuItems = new ArrayList<>(); + menuItems.add(new Dish("Chicken Rice", + new ArrayList<>(Arrays.asList(new Ingredient("Rice", 50, "g"), + new Ingredient("Chicken", 100, "g"))), 8.0F)); + menuItems.add(new Dish("Chicken Sandwich", + new ArrayList<>(Arrays.asList(new Ingredient("Lettuce", 100, "g"), + new Ingredient("Chicken", 50, "g"))), 5.0F)); + Menu menu = new Menu(menuItems); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String userInput = "delete 1"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + assertTrue(result instanceof DeleteDishCommand); + + DeleteDishCommand deleteDishCommand = (DeleteDishCommand) result; + int index = deleteDishCommand.index; + assertEquals(1, index); + } + + @Test + public void parseCommand_missingDeleteIndex_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "delete"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.MISSING_ARGUMENT_FOR_DELETE, feedbackToUser); + } + + @Test + public void parseCommand_notIntDeleteIndex_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "delete a"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.DISH_INDEX_NOT_INT, feedbackToUser); + } + + @Test + public void parseCommand_invalidDeleteIndex_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "delete -1"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_DISH_INDEX, feedbackToUser); + } + + @Test + public void parseCommand_deleteIndexOutOfBounds_returnsErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "delete 1"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_DISH_INDEX, feedbackToUser); + } + + //@@author ziyi105 + @Test + void parseCommand_unrecognisedInput_unknownCommand() { + Menu menu = new Menu(); + Dish testDish = new Dish("Chicken Rice", 2.50F); + menu.addDish(testDish); + String testUserInput = "random input"; + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + ParserUtil parserUtil = new Parser(); + Command commandReturned = parserUtil.parseCommand(menu, testUserInput, ui, pantry, sales, currentDate); + commandReturned.execute(); + assertEquals(ErrorMessages.UNKNOWN_COMMAND_MESSAGE, actualOutput.get(0)); + } + + @Test + void parseCommand_missingArgumentsForEditPrice_missingArgMsg() { + Menu menu = new Menu(); + Dish testDish = new Dish("Chicken Rice", 2.50F); + menu.addDish(testDish); + String testUserInput = "edit_price dish/1"; + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + ParserUtil parserUtil = new Parser(); + Command commandReturned = parserUtil.parseCommand(menu, testUserInput, ui, pantry, sales, currentDate); + commandReturned.execute(); + assertEquals(ErrorMessages.MISSING_ARGUMENT_FOR_EDIT_PRICE, actualOutput.get(0)); + } + + @Test + void parseCommand_missingArgumentsTypeForEditPrice_wrongArgMsg() { + Menu menu = new Menu(); + Dish testDish = new Dish("Chicken Rice", 2.50F); + menu.addDish(testDish); + String testUserInput = "edit_price dish/ price/4"; + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + ParserUtil parserUtil = new Parser(); + Command commandReturned = parserUtil.parseCommand(menu, testUserInput, ui, pantry, sales, currentDate); + commandReturned.execute(); + assertEquals(ErrorMessages.MISSING_DISH_IN_EDIT_PRICE, actualOutput.get(0)); + } + + @Test + void parseCommand_outOfBoundDishIndexForEditPrice_invalidIndexForEditPrice() { + Menu menu = new Menu(); + Dish testDish = new Dish("Chicken Rice", 2.50F); + menu.addDish(testDish); + String testUserInput = "edit_price dish/2 price/3"; + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + ParserUtil parserUtil = new Parser(); + Command commandReturned = parserUtil.parseCommand(menu, testUserInput, ui, pantry, sales, currentDate); + commandReturned.execute(); + assertEquals(ErrorMessages.INVALID_DISH_INDEX, actualOutput.get(0)); + } + + @Test + void parseCommand_nonDigitDishIndexForEditPrice_invalidIndexForEditPrice() { + Menu menu = new Menu(); + Dish testDish = new Dish("Chicken Rice", 2.50F); + menu.addDish(testDish); + String testUserInput = "edit_price dish/d price/3"; + + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + ParserUtil parserUtil = new Parser(); + Command commandReturned = parserUtil.parseCommand(menu, testUserInput, ui, pantry, sales, currentDate); + commandReturned.execute(); + assertEquals(ErrorMessages.WRONG_DISH_INDEX_TYPE_FOR_EDIT_PRICE, actualOutput.get(0)); + } + + //@@author DextheChik3n + @Test + void parseCommand_noArgumentsUserInput_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + Parser parser = new Parser(); + + String testInput = "12345"; + + Command outputCommand = parser.parseCommand(menu, testInput, ui, pantry, sales, currentDate); + + assertTrue(outputCommand instanceof IncorrectCommand); + } + + @Test + void parseCommand_dishWithOneIngredientForAddDish_dishAddedToMenu() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String addDishTestInput = "add name/Christmas Ham price/50.00 ingredient/Ham qty/1000g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for correct Command type returned + assertTrue(outputCommand instanceof AddDishCommand); + + //Test for 1 Dish added to Menu + outputCommand.execute(); + assertEquals(1, menu.getSize()); + + //Test for correct parsing of dish arguments + Dish getOutputDish = menu.getDishFromId(0); + + assertEquals("christmas ham", getOutputDish.getName()); + assertEquals((float) 50.0, getOutputDish.getPrice()); + assertEquals("[ham - 1000g]", getOutputDish.getIngredients().toString()); + } + + @Test + void parseCommand_dishWithThreeIngredientsForAddDish_dishContainsThreeIngredientAddedToMenu() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String addDishTestInput = "add name/Chicken Rice price/2.00 " + + "ingredient/rice qty/100g, ingredient/chicken qty/200g, ingredient/water qty/100ml"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for 3 Ingredients in the Dish added to Menu + outputCommand.execute(); + Dish actualDish = menu.getDishFromId(0); + assertEquals(3, actualDish.getIngredients().size()); + + //Test for correct parsing of dish arguments + Dish getOutputDish = menu.getDishFromId(0); + + assertEquals("chicken rice", getOutputDish.getName()); + assertEquals((float) 2.0, getOutputDish.getPrice()); + assertEquals("[rice - 100g, chicken - 200g, water - 100ml]", getOutputDish.getIngredients().toString()); + } + + @Test + void parseCommand_invalidArgumentInputForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + //input name/ argument wrongly + String addDishTestInput = "add named/Christmas Ham price/50.00 ingredient/Ham qty/1000g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_missingArgumentDishInputForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + //input name/ argument wrongly + String addDishTestInput = "add name/Christmas Ham price/50.00 ingredient/Ham"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_invalidQuantityUnitForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/Chicken Rice price/2.50 ingredient/rice qty/1 cup"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_negativeDishPriceForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/Chicken Rice price/-2.50 ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_negativeIngredientQtyForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/Chicken Rice price/2.50 ingredient/rice qty/-100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_emptyDishNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/ price/2.50 ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_tooLongDishNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/this dish name is probably too long for the test to pass oh noes " + + "price/2.50 ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_specialCharInDishNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/Ch1k3m R!ce price/2.50 ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_repeatedDishNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + Ingredient ingredient = new Ingredient("rice"); + ArrayList ingredients = new ArrayList<>(); + ingredients.add(ingredient); + menu.addDish(new Dish("chicken rice", ingredients, (float) 2.13)); + + String addDishTestInput = "add name/Chicken Rice price/2.50 ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(1, menu.getSize()); + } + + @Test + void parseCommand_whitespaceBetweenArgumentsForAddDish_dishAddedToMenu() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String addDishTestInput = "add name/ Christmas Ham price/ 50.00 ingredient/ Ham qty/ 1000g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for correct Command type returned + assertTrue(outputCommand instanceof AddDishCommand); + + //Test for 1 Dish added to Menu + outputCommand.execute(); + assertEquals(1, menu.getSize()); + + //Test for correct parsing of dish arguments + Dish getOutputDish = menu.getDishFromId(0); + + assertEquals("christmas ham", getOutputDish.getName()); + assertEquals((float) 50.0, getOutputDish.getPrice()); + assertEquals("[ham - 1000g]", getOutputDish.getIngredients().toString()); + } + + @Test + void parseCommand_repeatedIngredientArgumentForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/rice ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_repeatedQtyArgumentForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/rice qty/100g qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_missingIngredientNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300g, ingredient/ qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_veryLongIngredientNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300g " + + ", ingredient/this ingredient name is too long to fit inside the menu table qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_invalidIngredientQtyForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + //Test for negative ingredient qty + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300g " + + ", ingredient/rice qty/-100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + + //Test for more than max limit ingredient qty + addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300g " + + ", ingredient/rice qty/99999999g"; + outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_missingIngredientQtyUnitForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300 " + + ", ingredient/rice qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_specialCharInIngredientNameForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300g " + + ", ingredient/r!ce qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void parseCommand_repeatedIngredientNameInputForAddDish_incorrectCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String addDishTestInput = "add name/chicken rice price/2.50 ingredient/chicken qty/300g " + + ", ingredient/chicken qty/100g"; + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, addDishTestInput, ui, pantry, sales, currentDate); + + //Test for incorrect Command type returned + assertTrue(outputCommand instanceof IncorrectCommand); + + //Test for no dish added in menu + outputCommand.execute(); + assertEquals(0, menu.getSize()); + } + + @Test + void isRepeatedArgument_noRepeatArgument_false() { + String inputString = "name/christmas Ham price/50.00 ingredient/Ham qty/1000g"; + String inputArgument = "price/"; + assertFalse(Parser.isRepeatedArgument(inputString, inputArgument)); + } + + @Test + void isRepeatedArgument_repeatArgument_true() { + String inputString = "name/christmas Ham price/50.00 price/12.00 ingredient/Ham qty/1000g"; + String inputArgument = "price/"; + assertTrue(Parser.isRepeatedArgument(inputString, inputArgument)); + } + + @Test + void isRepeatedArgument_emptyString_true() { + String inputString = ""; + String inputArgument = "price/"; + assertFalse(Parser.isRepeatedArgument(inputString, inputArgument)); + } + + @Test + void isRepeatedArgument_emptyArgument_true() { + String inputString = "name/christmas Ham price/50.00 price/12.00 ingredient/Ham qty/1000g"; + String inputArgument = ""; + assertTrue(Parser.isRepeatedArgument(inputString, inputArgument)); + } + + @Test + void isRepeatedArgument_nullInput_true() throws NullPointerException { + String inputString = "name/christmas Ham price/50.00 price/12.00 ingredient/Ham qty/1000g"; + String inputArgument = "price/"; + assertThrows(NullPointerException.class, () -> Parser.isRepeatedArgument(null, inputArgument)); + assertThrows(NullPointerException.class, () -> Parser.isRepeatedArgument(inputString, null)); + } + + @Test + void parsePriceToFloat_validPriceString_exactFloatValue() throws ParserException { + String inputPriceString = "3.1"; + + assertEquals((float) 3.1, Parser.parsePriceToFloat(inputPriceString)); + } + + @Test + void parsePriceToFloat_largePriceString_parserExceptionThrown() { + String inputPriceString = "99999999999.99"; + + assertThrows(ParserException.class, () -> Parser.parsePriceToFloat(inputPriceString)); + } + + @Test + void parsePriceToFloat_moreThanTwoDPPriceString_parserExceptionThrown() { + String inputPriceString = "1.9999"; + + assertThrows(ParserException.class, () -> Parser.parsePriceToFloat(inputPriceString)); + } + + @Test + void parsePriceToFloat_whitespaceInPriceString_exactFloatValue() throws ParserException { + String inputPriceString = " 1.99 "; + + assertEquals((float) 1.99, Parser.parsePriceToFloat(inputPriceString)); + } + + @Test + void parsePriceToFloat_negativePriceString_parserExceptionThrown() { + String inputPriceString = "-1.99"; + + assertThrows(ParserException.class, () -> Parser.parsePriceToFloat(inputPriceString)); + } + + @Test + void parsePriceToFloat_emptyPriceString_parserExceptionThrown() { + String inputPriceString = ""; + + assertThrows(ParserException.class, () -> Parser.parsePriceToFloat(inputPriceString)); + } + + @Test + void isRepeatedDishName_existingDishName_true() { + Menu menu = new Menu(); + Dish dish = new Dish("Chicken Rice", 2.50F); + menu.addDish(dish); + + String inputDishName = "chicken rice"; + + assertTrue(Parser.isRepeatedDishName(inputDishName, menu)); + } + + @Test + void isRepeatedDishName_nonExistingDishName_false() { + Menu menu = new Menu(); + Dish dish = new Dish("Chicken Rice", 2.50F); + menu.addDish(dish); + + String inputDishName = "chicken chop"; + + assertFalse(Parser.isRepeatedDishName(inputDishName, menu)); + } + + @Test + void isRepeatedDishName_nullString_nullPointerExceptionThrown() throws NullPointerException { + Menu menu = new Menu(); + Dish dish = new Dish("Chicken Rice", 2.50F); + menu.addDish(dish); + + assertThrows(NullPointerException.class, () -> Parser.isRepeatedDishName(null, menu)); + } + + @Test + void isRepeatedDishName_emptyDishName_false() { + Menu menu = new Menu(); + Dish dish = new Dish("Chicken Rice", 2.50F); + menu.addDish(dish); + + String inputDishName = ""; + + assertFalse(Parser.isRepeatedDishName(inputDishName, menu)); + } + + @Test + void isRepeatedIngredientName_nonExistingIngredientName_false() { + ArrayList ingredients = new ArrayList<>(); + Ingredient ingredient = new Ingredient("rice"); + ingredients.add(ingredient); + + assertFalse(Parser.isRepeatedIngredientName("apple", ingredients)); + } + + @Test + void isRepeatedIngredientName_existingIngredientName_true() { + ArrayList ingredients = new ArrayList<>(); + Ingredient ingredient = new Ingredient("rice"); + ingredients.add(ingredient); + + assertTrue(Parser.isRepeatedIngredientName("rice", ingredients)); + } + + @Test + void isRepeatedIngredientName_nullIngredientName_nullPointerExceptionThrown() { + ArrayList ingredients = new ArrayList<>(); + + assertThrows(NullPointerException.class, () -> Parser.isRepeatedIngredientName(null, ingredients)); + } + + @Test + void isNameLengthInvalid_moreThanMaxLengthString_true() { + assertTrue(Parser.isNameLengthInvalid("this string is more than 35 characters")); + } + + @Test + void isNameLengthInvalid_lessThanMaxLengthString_false() { + assertFalse(Parser.isNameLengthInvalid("this str is less than 35 chars")); + } + + @Test + void isNameLengthInvalid_nullString_nullPointerExceptionThrown() throws NullPointerException { + assertThrows(NullPointerException.class, () ->Parser.isNameLengthInvalid(null)); + } + + //@@author ShaniceTang + @Test + void parseCommand_returnViewTotalStockCommandClass() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String userInput = "view_stock"; + + ParserUtil parserUtil = new Parser(); + Command outputCommand = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + ViewTotalStockCommand viewTotalStockCommand = new ViewTotalStockCommand(pantry, ui); + + assertEquals(viewTotalStockCommand.getClass(), outputCommand.getClass()); + } + + @Test + void parseCommand_missingArgsForBuyIngredient_returnErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + + String userInput = "buy_ingredient"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_INGREDIENT_ARGUMENTS, feedbackToUser); + } + + @Test + void parseCommand_invalidArgsForBuyIngredient_returnErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "buy_ingredient ingredient/rice qty/5 cups"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_UNIT_MESSAGE, feedbackToUser); + } + + @Test + void isValidUnit_invalidUnit_returnFalse() { + assertFalse(Parser.isValidUnit("kg")); + } + + @Test + void isEmptyUnit_emptyUnit_returnTrue() { + assertTrue(Parser.isEmptyUnit("")); + } + + @Test + void checkForMismatchUnit_mismatchingUnit_throwParserException() { + Menu menu = new Menu(); + ArrayList ingredients = new ArrayList<>(); + ingredients.add(new Ingredient("chicken", 500, "g")); + Dish dish = new Dish("chicken rice", ingredients, 3.00F); + menu.addDish(dish); + + Ingredient ingredient = new Ingredient("chicken", 500, "ml"); + + assertThrows(ParserException.class, () -> Parser.checkForMismatchUnit(menu, ingredient)); + } + //@@author + + @Test + void parseCommand_listTotalSalesCommand_returnInstanceOfListTotalSalesCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_total_sales"; + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof ListTotalSalesCommand); + } + + @Test + void parseCommand_validListSaleIndex_returnInstanceOfListSaleByDayCommand() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_sale day/1"; + + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof ListSaleByDayCommand); + + ListSaleByDayCommand listSaleByDayCommand = (ListSaleByDayCommand) result; + int day = listSaleByDayCommand.getDay(); + assertEquals(1, day); + } + + @Test + void parseCommand_invalidListSaleIndex_showErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_sale day/a"; + + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_DAY_FORMAT, feedbackToUser); + } + + @Test + void parseCommand_missingListSaleIndex_showErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_sale day/"; + + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_SHOW_SALE_DAY_FORMAT_MESSAGE + + ListSaleByDayCommand.MESSAGE_USAGE, feedbackToUser); + } + + @Test + void parseCommand_invalidListSaleFormat_showErrorMessage() { + Menu menu = new Menu(); + Ui ui = new Ui(); + Pantry pantry = new Pantry(ui); + Sales sales = new Sales(); + CurrentDate currentDate = new CurrentDate(); + String userInput = "list_sale /1"; + + ParserUtil parserUtil = new Parser(); + Command result = parserUtil.parseCommand(menu, userInput, ui, pantry, sales, currentDate); + + assertTrue(result instanceof IncorrectCommand); + + IncorrectCommand incorrectCommand = (IncorrectCommand) result; + String feedbackToUser = incorrectCommand.feedbackToUser; + assertEquals(ErrorMessages.INVALID_SHOW_SALE_DAY_FORMAT_MESSAGE + + ListSaleByDayCommand.MESSAGE_USAGE, feedbackToUser); + } +} diff --git a/src/test/java/seedu/cafectrl/storage/DecoderTest.java b/src/test/java/seedu/cafectrl/storage/DecoderTest.java new file mode 100644 index 0000000000..43d2deb425 --- /dev/null +++ b/src/test/java/seedu/cafectrl/storage/DecoderTest.java @@ -0,0 +1,109 @@ +package seedu.cafectrl.storage; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.data.Pantry; +import seedu.cafectrl.data.dish.Ingredient; +import seedu.cafectrl.ui.Ui; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author ziyi105 +public class DecoderTest { + @Test + void decodePantryStockData_validData_pantryFilledWithStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + validPantryStockList.add(new Ingredient("egg", 1, "g")); + validPantryStockList.add(new Ingredient("milk", 1, "ml")); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("egg | 1 | g"); + fileDataList.add("milk | 1 | ml"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } + + @Test + void decodePantryStockData_missingDivider_skipStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("milk 1 | ml"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } + + @Test + void decodePantryStockData_specialCharInIngredientName_skipStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("milk| | 1 | ml"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } + + @Test + void decodePantryStockData_invalidQtyType_skipStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("milk | one | ml"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } + + @Test + void decodePantryStockData_extraDividerAfterName_skipStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("milk || 1 | ml"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } + + @Test + void decodePantryStockData_invalidUnit_skipStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("milk | 1 | glass"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } + + @Test + void decodePantryStockData_repeatedIngredientName_skipStock() { + Ui ui = new Ui(); + ArrayList validPantryStockList = new ArrayList<>(); + Pantry samplePantry = new Pantry(ui, validPantryStockList); + samplePantry.addIngredientToStock("milk", 1, "ml"); + + ArrayList fileDataList = new ArrayList<>(); + fileDataList.add("milk | 1 | ml"); + fileDataList.add("milk | 2 | ml"); + Pantry testPantry = Decoder.decodePantryStockData(fileDataList); + + assertEquals(samplePantry.getPantryStock(), testPantry.getPantryStock()); + } +} diff --git a/src/test/java/seedu/cafectrl/storage/FileManagerTest.java b/src/test/java/seedu/cafectrl/storage/FileManagerTest.java new file mode 100644 index 0000000000..09cd0b15ad --- /dev/null +++ b/src/test/java/seedu/cafectrl/storage/FileManagerTest.java @@ -0,0 +1,64 @@ +package seedu.cafectrl.storage; + +import org.junit.jupiter.api.Test; +import seedu.cafectrl.ui.ErrorMessages; +import seedu.cafectrl.ui.Ui; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Junit test for FileManager.java + */ +public class FileManagerTest { + @Test + public void readTextFile_emptyFilePath_fileNotFoundExceptionThrown() { + String inputFilePath = ""; + + FileManager fileManager = new FileManager(new Ui()); + assertThrows(FileNotFoundException.class, () -> fileManager.readTextFile(inputFilePath)); + } + + @Test + public void readTextFile_nullFilePath_nullPointerExceptionThrown() { + + FileManager fileManager = new FileManager(new Ui()); + assertThrows(NullPointerException.class, () -> fileManager.readTextFile(null)); + } + + @Test + public void checkFileExists_emptyFilePath_fileNotFoundExceptionThrown() { + String inputFilePath = ""; + + FileManager fileManager = new FileManager(new Ui()); + assertThrows(Exception.class, () -> fileManager.checkFileExists(inputFilePath)); + } + + @Test + public void checkFileExists_nullFilePath_nullPointerExceptionThrown() { + FileManager fileManager = new FileManager(new Ui()); + assertThrows(NullPointerException.class, () -> fileManager.checkFileExists(null)); + } + + @Test + public void overwriteFile_emptyFilePath_emptyFileInputMessage() { + ArrayList actualOutput = new ArrayList<>(); + Ui ui = new Ui() { + @Override + public void showToUser(String... message) { + actualOutput.addAll(Arrays.asList(message)); + } + }; + FileManager fileManager = new FileManager(ui); + + String inputFilePath = ""; + ArrayList inputTextList = new ArrayList<>(); + + fileManager.overwriteFile(inputFilePath, inputTextList); + assertEquals(ErrorMessages.MISSING_FILEPATH, actualOutput.get(0)); + } +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..9edaa58833 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,21 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +Menu data was not found! +No worries, new menu has been created + + +Pantry stock data was not found! +No worries, new pantry has been created + + +Order list data was not found! +No worries, new order list has been created + + +Hello! Welcome to CafeCTRL! +----------------------------------------------------- +> ----------------------------------------------------- +Goodbye <3 Have a great day ahead! +----------------------------------------------------- +Data Folder was not found! +It's ok... a new data folder has been created. + + diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..b023018cab 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1 @@ -James Gosling \ No newline at end of file +bye