diff --git a/build.gradle b/build.gradle index b0c5528fb5..91fffd027d 100644 --- a/build.gradle +++ b/build.gradle @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 7e1ce222a9..e8297cd72e 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -185,7 +185,7 @@ - + diff --git a/data/order.txt b/data/order.txt new file mode 100644 index 0000000000..f6c46e97fb --- /dev/null +++ b/data/order.txt @@ -0,0 +1,7 @@ +1|PANADOL|100|09-10-2021|PENDING +2|VICODIN|30|09-10-2021|PENDING +3|VICODIN|50|10-10-2021|DELIVERED +4|SIMVASTATIN|20|11-10-2021|PENDING +5|LISINOPRIL|200|12-10-2021|PENDING +6|AZITHROMYCIN|100|13-10-2021|PENDING +7|PANADOL|50|13-10-2021|PENDING diff --git a/data/order_archive.txt b/data/order_archive.txt new file mode 100644 index 0000000000..e07b9103d9 --- /dev/null +++ b/data/order_archive.txt @@ -0,0 +1 @@ +[ORDER ID: 3] 50 VICODIN WAS ORDERED ON 10-10-2021. STATUS: DELIVERED diff --git a/data/prescription.txt b/data/prescription.txt new file mode 100644 index 0000000000..c3e0a4c3a6 --- /dev/null +++ b/data/prescription.txt @@ -0,0 +1,5 @@ +1|PANADOL|10|S1234567A|09-10-2021|Jane|1 +2|VICODIN|10|S2345678B|10-10-2021|Peter|3 +3|SIMVASTATIN|10|S1234567A|11-10-2021|Sam|4 +4|LISINOPRIL|10|S3456789C|12-10-2021|Jane|5 +5|AZITHROMYCIN|10|S2345678B|13-10-2021|Peter|6 diff --git a/data/prescription_archive.txt b/data/prescription_archive.txt new file mode 100644 index 0000000000..b4f854314b --- /dev/null +++ b/data/prescription_archive.txt @@ -0,0 +1,2 @@ +[PRESCRIPTION ID: 1] 10 PANADOL [STOCK ID: 1] WAS PRESCRIBED BY JANE TO S1234567A ON 09-10-2021 +[PRESCRIPTION ID: 2] 10 VICODIN [STOCK ID: 3] WAS PRESCRIBED BY PETER TO S2345678B ON 10-10-2021 diff --git a/data/stock.txt b/data/stock.txt new file mode 100644 index 0000000000..95526f894a --- /dev/null +++ b/data/stock.txt @@ -0,0 +1,6 @@ +1|PANADOL|20.0|20|13-09-2021|BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS|1000|false +2|PANADOL|20.0|10|14-09-2021|BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS|1000|false +3|VICODIN|10.0|20|30-09-2021|POPULAR DRUG FOR TREATING ACUTE OR CHRONIC MODERATE TO MODERATELY SEVERE PAIN|500|false +4|SIMVASTATIN|20.0|25|10-10-2021|TREATS HIGH CHOLESTEROL AND REDUCES THE RISK OF STROKE|800|false +5|LISINOPRIL|20.0|25|15-10-2021|USED FOR TREATING HYPOTHYROIDISM|800|false +6|AZITHROMYCIN|20.0|35|15-10-2021|USED FOR TREATING EAR, THROAT, AND SINUS INFECTIONS|100|false diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..24b0c160dd 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -2,8 +2,10 @@ 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) +![](https://via.placeholder.com/100.png?text=Photo) | Alvin Tan Guo Hao | [Github](https://github.com/alvintan01) | [Portfolio](team/alvintan01.md) +![](https://via.placeholder.com/100.png?text=Photo) | Deon Chung Hui | [Github](https://github.com/deonchung) | [Portfolio](team/deonchung.md) +![](https://via.placeholder.com/100.png?text=Photo) | Jiang Weichen | [Github](https://github.com/jiangweichen835) | [Portfolio](team/jiangweichen835.md) +![](https://via.placeholder.com/100.png?text=Photo) | Teo Chin Kai Remus | [Github](https://github.com/RemusTeo) | [Portfolio](team/remusteo.md) +![](https://via.placeholder.com/100.png?text=Photo) | Teo Phing Huei, Aeron | [Github](https://github.com/a-tph) | [Portfolio](team/a-tph.md) + + diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..b759d3fbe4 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,708 @@ # Developer Guide +## Introduction + +MediVault is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. +It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. The +purpose of this guide is to help developers set up and continue with the development of MediVault past version 2.1. + ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* Inspiration for App Idea and OOP Structure: [https://github.com/se-edu/addressbook-level2](https://github.com/se-edu/addressbook-level2) +* Inspiration for User Guide: [https://se-education.org/addressbook-level3/UserGuide.html](https://se-education.org/addressbook-level3/UserGuide.html) +* Inspiration for Developer Guide: [https://se-education.org/addressbook-level3/DeveloperGuide.html](https://se-education.org/addressbook-level3/DeveloperGuide.html) +* PlantUML Tutorial: [https://se-education.org/guides/tutorials/plantUml.html](https://se-education.org/guides/tutorials/plantUml.html) +* Gradle: [https://github.com/gradle/gradle](https://github.com/gradle/gradle) + +## Contents + +* [Glossary](#glossary) +* [Setting up environment](#setting-up-environment) + * [Setting up](#setting-up) + * [Before writing code](#before-writing-code) +* [Design](#design) + * [Architecture](#architecture) + * [Command](#command) + * [Utilities](#utilities) + * [Validator](#validator) + * [Storage](#storage) + * [Inventory](#inventory) + * [Errors](#errors) +* [Implementation](#implementation) + * [Main Logic](#main-logic) + * [List Command](#list-command) + * [Stock Commands](#stock-commands) + * [AddStockCommand](#addstockcommand) + * [DeleteStockCommand](#deletestockcommand) + * [UpdateStockCommand](#updatestockcommand) + * [Prescription Commands](#prescription-commands) + * [AddPrescriptionCommand](#addprescriptioncommand) + * [DeletePrescriptionCommand](#deleteprescriptioncommand) + * [UpdatePrescriptionCommand](#updateprescriptioncommand) + * [Order Commands](#order-commands) + * [AddOrderCommand](#addordercommand) + * [DeleteOrderCommand](#deleteordercommand) + * [UpdateOrderCommand](#updateordercommand) + * [ReceiveOrderCommand](#receiveordercommand) + * [Archive Commands](#archive-commands) + * [ArchivePrescriptionCommand](#archiveprescriptioncommand) + * [ArchiveOrderCommand](#archiveordercommand) +* [Product Scope](#product-scope) + * [Target User Profile](#target-user-profile) + * [Value Proposition](#value-proposition) +* [User Stories](#user-stories) +* [Non-Functional Requirements](#non-functional-requirements) +* [Instructions for Manual Testing](#instructions-for-manual-testing) +* [Instructions for Automated Testing](#instructions-for-automated-testing) + +## Glossary + +Terminology | Meaning +------ | ------ +Stock | Refers to a medication. +Prescription | Refers to a prescription. +Order | Refers to ordering new medications to replenish the stocks. +Parameters | Prefixes for MediVault to understand the type of information you provide. + +Meaning of Icons: +- :information_source: Note +- :warning: Warning +- :bulb: Tip + +## Setting up environment + +### Setting up + +1. Fork [this](https://github.com/AY2122S1-CS2113T-T10-1/tp/) repo, and clone the fork into your computer. +2. Ensure that you have [IntelliJ IDEA](https://www.jetbrains.com/idea/download/#section=windows) + and [JDK 11](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html) installed. +3. Configure the JDK + * Follow the guide + at [se-edu/guides IDEA: Configuring the JDK](https://se-education.org/guides/tutorials/intellijJdk.html) to ensure + Intellij is configured to use JDK 11. +4. Import the project as a Gradle project + * Follow the + guide [se-edu/guides IDEA: Importing a Gradle project](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) + to import the project into IDEA. + * Note: Importing a Gradle project is slightly different from importing a normal Java project. +5. Verify the setup + * Locate the file `src/main/java/MediVault.java` then run the `MediVault.main()` and try a few commands + * Run the [test](https://se-education.org/addressbook-level3/Testing.html) to ensure they all pass. + +### Before writing code + +1. Configure the coding style + * If using IDEA, follow the + guide [se-edu/guides IDEA: Configuring the code style](https://se-education.org/guides/tutorials/intellijCodeStyle.html) + to set up IDEA’s coding style to match ours. +2. Set up CI + * This project comes with a GitHub Actions config files (in `.github/workflows folder`). When GitHub detects those + files, it runs the CI for your project automatically at each push to the `master` branch or to any PR. No set + up required. + +## Design + +### Architecture + +The **Architecture Diagram** for MediVault is shown below. + +![ArchitectureDiagram](diagrams/diagram_images/ArchitectureDiagram.png) + +A quick overview of the main components and how they interact with each other is given below. + +The main class that runs MediVault is called `MediVault`. It is responsible for, +* At program launch: Initializes the components in the correct sequence, and connects them up with each other. +* At shut down: Shuts down the components and invokes cleanup methods where necessary. + +The rest of the program consist of four components. +* `Command`: Executes command based on the user input that is processed by `Utilities` + component. The list of commands can be found in our User Guide [here](UserGuide.md). +* `Utilities`: Contains important driver classes for MediVault. + * Includes `parser`, `ui`, `storage` and `comparators`. +* `Inventory`: Contains a collection of classes used by MediVault to represent +different medication information. +* `Errors`: Contains collection of classes that handles exceptions during execution of MediVault. + +### Command + +![CommandClassDiagram](diagrams/diagram_images/CommandClassDiagram.png) + +The **Command** class diagram above shows how **Command** interact with other classes in MediVault. + +The Command Component consists of **18** subclasses where each subclass represents a command feature. + +Let `*` be either of the three class: `Stock`, `Prescription` or `Order`. + +* `Add*Command`: Adds a new `*` information into MediVault. +* `Delete*Command`: Removes the visibility of the `*` record in MediVault. +* `Update*Command`: Updates the `*` information. +* `List*Command`: Lists the `*` records. +* `ReceiveOrderCommand`: Marks an order as received and adds the ordered medication into the current stocks. +* `ArchivePresciptionCommand`: Archives all the prescription records before a given date. +* `ArchiveOrderCommand`: Archives all the order records before a given date. +* `PurgeCommand`: Wipes all records in MediVault. +* `HelpCommand`: Shows the help page. +* `ExitCommand`: Exits MediVault. + +### Utilities + +#### Validator + +The class diagram below shows how the validator classes is implemented to help ensure that the user input is +valid. `StockValidator`, `PrescriptionValidator` and `OrderValidator` inherits from `MedicineValidator`. The class +methods are also shown in the diagram. + +![ValidatorClassDiagram](diagrams/diagram_images/ValidatorClassDiagram.png) + +#### Storage + +The `Storage` component of MediVault is implemented for purpose of loading, storing and archiving of data. Basically, it +handles all file related processes necessary for MediVault to function. After every operation that modifies the stock, +prescription or order, data is automatically and dynamically saved into the corresponding data files. + +`Storage` class is associated with `FileParser` class because during startup of MediVault, data is loaded, +and validation is done with methods in FileParser. + +`FileParser` class handles validation of data of the files `data/stock.txt`, `data/prescription.txt` and +`data/order.txt`. If it detects anything invalid, it throws an exception with specific information about which row +and which file the invalid data is. + +> :information_source: Note: +> * MediVault cannot start up until data in data files are deemed valid. +> * This is to prevent invalid data from entering the system caused by direct tampering of data files. + +The class diagram below shows the Storage component of MediVault. + +![StorageClassDiagram](diagrams/diagram_images/StorageClassDiagram.png) + +### Inventory + +The class diagram below shows how the objects in MediVault is implemented. `Stock`, `Prescription` +and `Order` inherits from the abstract `Medicine` class. The attributes that each object has is also shown in the +diagram. + +![InventoryClassDiagram](diagrams/diagram_images/InventoryClassDiagram.png) + +### Errors + +- `InvalidCommandException` is thrown when the user enters an invalid command. +- `InvalidDataException` is thrown when MediVault encountered invalid data in the data files. + +## Implementation + +### Main Logic + +The main application logic shows how the commands are handled throughout the application. Below is the outline of the +logic: + +* MediVault is called by the `main` method which calls the constructor of MediVault. Data is then loaded from the + `Storage` class to the application. +* MediVault gets the user input via the `Ui` class and uses the `CommandParser` class to parse the input given by the + user. +* The parameters is parsed to a `LinkedHashMap` to make the parameters easily accessible. +* If a valid command is received, the `CommandParser` calls the `Command` object constructor and return the object + to MediVault. +* MediVault invokes the `execute()` function of the `Command` object to execute the command. + +> :warning: Warning: +> * Should there be an invalid command, `CommandParser` throws `InvalidCommandException` and MediVault displays the error message using the `Ui` class. + +Given below is the sequence diagram after `run()` is called for the interactions within the main application logic. + +![MainLogicSequenceDiagram](diagrams/diagram_images/MainLogicSequenceDiagram.png) +- `changeMode()` is called when the user entered `stock`, `prescription` or `order` to help change modes. +- `processCommand()` helps to parse the user's command to a `Command` object. +- `parseParameters()` returns all the parameters entered as a `LinkedHashmap`. This helps to make the +parameters entered by the user easily accessible by the `Command` objects. + +After the `.execute()` command is called, MediVault does the following validator checks as shown below. + +![ContainValidParametersAndValuesSequenceDiagram](diagrams/diagram_images/ContainValidParametersAndValuesSequenceDiagram.png) + +> :information_source: Note: Replace `*` with `Stock`, `Prescription` or `Order` depending on the command entered. + +1. `*Command` attempts to get the instances of the `Ui` and `Medicine` classes which are a singletons if they exists. +Otherwise, it creates a new instance of the `Ui` and/or `Medicine` class. +2. `*Command` creates a new `*Validator` instance which contains the methods to validate the user's input for the +respective `*`. +3. `*Command` runs `containsInvalidParametersAndValues()` and does validation checks explained in detail in **Step 4** +and **Step 5**. +4. The `MedicineValidator` class runs `containsInvalidParameters()` to check if parameters input by the user are valid. +5. Then, `MedicineValidator` class runs `containsInvalidParameterValues()` in `*Validator` to check if +parameter values input by the user is valid. +6. `MedicineValidator` returns the result of the validity checks back to `*Command`. +7. After running the Logic for `*Command`, commands that modifies the `*` information attempts to get the instance of +`Storage` class which is a singleton if it exists. MediVault runs `saveData()` to save the latest information into the text file. + +The motivation to implement an **initial validity checker** was because most of the commands requires MediVault to check +if user input provided by the user are valid. This **guarantees** that the parameters and parameter values provided by +the user are valid after it passes the validity checks. + +The logic for all the `*Command` are further elaborated below. + +### List Command + +There are three variations of the list command. + +1. `liststock` +2. `listprescription` +3. `listorders` + +The sequence diagram below shows how the `list` operation works in general. + +![ListSequenceDiagram](diagrams/diagram_images/ListSequenceDiagram.png) + +* All three variations of `list` are similar as they are implemented by iterating through the `Medicine` ArrayList and + filtering out the respective object types. +* If the parameter `sort` or `rsort` is provided, the respective constructor of the `Comparator` classes is invoked + to help sort the ArrayList. +* For the rest of the valid command parameters, MediVault does a **contains** comparison for strings and **equals** + comparison for integers as well as dates except for `expiring` and `low` parameters where it does a **less than or + equal** comparison. +* `getAttributeValue()` represents all the get methods available in each of the respective classes. At the end of the + execution the respective `print()` method from the `Ui` class is called to display the respective tables. + +### Stock Commands + +#### AddStockCommand + +MediVault creates an `AddStockCommand` object when `CommandParser` identifies `addstock` or `add` in `stock` +mode. +> :information_source: Note: +> * MediVault adds medicine stock when the `parameter` and `parameterValues` provided by the user are valid. +> * Users cannot input medication if `max_quantity` is less than `quantity`. +> * MediVault ignores the `price`, `description` and `max_quantity` of user input if the same medication name and expiry date already exist. +> * MediVault ignores the `description` and `max_quantity` of user input if the same medication name already exist. + +The sequence diagram for `AddStockCommand` is shown below. + +![AddStockSequenceDiagram](diagrams/diagram_images/AddStockSequenceDiagram.png) + +MediVault determines if there exist the medication with the same name. +* If there exist medication with the same name, MediVault checks if there exist the same expiry date using the `isExpiryExist()` method. + * MediVault then checks if the quantity is valid using the `isValidQuantity()` method. + * If the same name and expiry date exist, Medivault updates the quantity of the existing stock. + * If the same expiry date does not exist, MediVault adds the medication using the existing description and maximum quantity. +* If the same medication does not exist in MediVault, MediVault then checks if the quantity is valid using the `isValidQuantity()` method and a new medication is added. + +#### DeleteStockCommand + +MediVault creates an `DeleteStockCommand` object when `CommandParser` identifies `deletestock` or the `delete` keyword +in `stock` mode. + +* MediVault allows deletion of a stock by specifying stock id through `i/ID`. +* MediVault allows deletion of expiring stocks by specifying an expiry date through `expiring/EXPIRY_DATE`. + +> :information_source: Note: +> * MediVault deletes medicine stock information when `parameter` and `parameterValues` provided by the user are valid. +> * MediVault performs a check to determine if it is executing deletion by stock id or deletion by expiry then executes + accordingly. +> * MediVault does not execute if both id and expiry date are specified. +> * MediVault does not actually delete the stock. Rather, it sets it as deleted and hide from user view. This is +for the purpose of retaining stock information in case it is needed again in the future. +> * For example, if a prescription was deleted, the information of the medicine is still intact even if the stock was deleted. + +The sequence diagram for `DeleteStockCommand` is shown below. + +![DeleteStockSequenceDiagram](diagrams/diagram_images/DeleteStockSequenceDiagram.png) + +If MediVault determines that it is executing deletion by stock id, it executes accordingly. Currently, it only +allows for deletion of 1 stock at a time. + +The sequence diagram for deletion by stock id is shown below. + +![DeletionOfStockByIdSequenceDiagram](diagrams/diagram_images/DeletionOfStockByIdSequenceDiagram.png) + +* `deleteStockById()` helps to delete a stock given an id. + * Loops through all medicines to `getStockId()` to compare and get the specified stock. + * Then call `setDeleted()` to delete the stock. + +If MediVault determines that it is executing deletion by expiry date, it executes accordingly. The behaviour of +this command is to delete all stock before or equals to the specified date. This is because we would want to delete all +expired stock and if a date is specified, all the date before is also expired. + +The sequence diagram for delete by expiry date is shown below. + +![DeletionOfStockByIdSequenceDiagram](diagrams/diagram_images/DeletionOfStockByExpirySequenceDiagram.png) + +* `deleteStockByExpiry()` helps to delete stocks given an expiry date. + * `stringToDate()` helps to parse a string to a Date object. + * Loops through all medicines to `getExpiry()` to compare and get the all expired stock. + * Then calls `setDeleted()` to delete the stock. + +#### UpdateStockCommand + +MediVault creates an `UpdateStockCommand` object when `CommandParser` identifies `updatestock` or +the `update` keyword in `stock` mode. -## Design & implementation +The sequence diagram for `UpdateStockCommand` is shown below. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +![UpdateStockSequenceDiagram](diagrams/diagram_images/UpdateStockSequenceDiagram.png) +MediVault retrieves the stock object using the `i/ID` parameter specified by the user using the `extractStockObject()` +method. MediVault conducts another validation check on the provided `q/QUANTITY`,`m/MAX_QUANTITY` and `e/EXPIRY_DATE` +against the stock object retrieved earlier. This validation check is separated from the initial validation checker +as enforcing `q/QUANTITY <= m/MAX_QUANTITY` can only be done **after** MediVault confirms what user input is +provided. This is because the backend processing for either one or both parameters provided by the user are different. -## Product scope -### Target user profile +MediVault adds a new stock record when a user update contains the `n/NAME` parameter. The old stock record still +exists in MediVault, but it is not visible to the user when listed. This approach solves the issue when a user is +unable to delete a prescription record when the medicine stock name gets updated. -{Describe the target user profile} +### Prescription Commands -### Value proposition +#### AddPrescriptionCommand -{Describe the value proposition: what problem does it solve?} +MediVault creates an `AddPrescriptionCommand` object when `CommandParser` identifies `addprescription` or `add` in `prescription` mode. + +> :information_source: Note: +> * MediVault adds the prescription when the `parameter` and `parameterValues` provided by the user are valid. +> * MediVault updates the quantity left in the stock automatically after prescribing. +> * MediVault prescribes medication with the earliest date if there are medication with multiple expiry dates. +> * Users cannot prescribe expired medication. +> * Users cannot prescribe medication if the quantity is more than the total stock quantity. + +The sequence diagram for `AddPrescriptionCommand` is shown below. + +![AddPrescriptionCommandDiagram](diagrams/diagram_images/AddPrescriptionSequenceDiagram.png) + +- `prescribe()` method changes the stock quantity based on prescription quantity and add prescribed medication to prescription list. + +#### DeletePrescriptionCommand + +MediVault creates a `DeletePrescriptionCommand` object when `CommandParser` identifies `deleteprescription` or +`delete` in `prescription` mode. + +> :information_source: Note: +> * MediVault deletes the prescription when the `parameter` and `parameterValues` provided by the user are valid. +> * MediVault deletes the prescription based on the user input of `PRESCRIPTION_ID`. +> * MediVault adds the prescription quantity to the stock quantity after successful deletion of prescription. +> * Users cannot delete prescriptions if the total stock quantity after restoration is more than the maximum + quantity. +> * If the stock is deleted, MediVault recovers the stock and add the prescription quantity to the stock. + +The sequence diagram for `DeletePrescriptionCommand` is shown below. + +![DeletePrescriptionCommandDiagram](diagrams/diagram_images/DeletePrescriptionSequenceDiagram.png) + +- `setStockQuantity()` method helps to check if a stock exists. If the stock exists, it adds the prescribed quantity to the current stock quantity. +- `isValidPrescriptionParameters()` helps to ensure that the parameters for the prescription to be deleted are valid. + +#### UpdatePrescriptionCommand + +MediVault initialises an `UpdatePrescriptionCommand` class when `CommandParser` identifies +`updateprescription` or the `update` keyword in `prescription` mode. + +The sequence diagram for `UpdatePrescriptionCommand` is shown below. + +![UpdatePrescriptionSequenceDiagram](diagrams/diagram_images/UpdatePrescriptionSequenceDiagram.png) + +MediVault retrieves the prescription object using the `i/ID` parameter specified by the user using the +`extractPrescriptionObject()` method. + +The main update logic is split into four sections. +1. User provided both `n/NAME` and `q/QUANTITY` parameters. + 1. MediVault restores the stock quantity for the **original** `n/NAME` with the **original** `q/QUANTITY`. + 2. MediVault decrements the stock quantity for the **updated** `n/NAME` with the **updated** `q/QUANTITY`. +2. User provided `n/NAME` parameter but not `q/QUANTITY`. + 1. MediVault restores the stock quantity for the **original** `n/NAME` with the `q/QUANTITY` present in the + prescription object. + 2. MediVault decrements the stock quantity for the **updated** `n/NAME` with the `q/QUANTITY` present in the + prescription object. +3. User provided `q/QUANTITY` parameter but not `n/NAME` + 1. If the **updated** `q/QUANTITY` is more than the **original** `q/QUANTITY` MediVault decrements the stock quantity + for `n/NAME` present in the prescription object with the additional `q/QUANTITY` which is the difference between the + **updated** and **original** `q/QUANTITY`. + 2. Otherwise, MediVault restores the stock quantity for `n/NAME` present in the prescription object with the + difference between the **updated** and **original** `q/QUANTITY`. +4. User did not provide both `q/QUANTITY` and `n/NAME` parameter. + 1. Restoring or decrement is not needed. + +Other parameters like `d/DATE`, `c/CUSTOMER_ID` and `s/STAFF` are not affected because they share the same +update logic for sections 1 to 4 mentioned above. + +MediVault adds a new prescription record when a user updates contains either the `n/NAME`, `q/QUANTITY` +parameter or both. The old prescription record is **permanently removed** from MediVault. + +This approach solves the issue when a medication is prescribed to a user with an amount that is +**more than** the current batch of stock with the same Stock ID but **less than** the total +stock quantity. + +> :information_source: Note: MediVault automatically adds new prescription records when a medication is prescribed +> from stocks with different Stock IDs. + +### Order Commands + +#### AddOrderCommand + +MediVault creates an `AddOrderCommand` object when `CommandParser` identifies `addorder` or the `add` keyword +in `order` mode. + +> :information_source: Note: +> * MediVault adds order information when `parameter` and `parameterValues` provided by the user are valid. +> * As the order date is an optional parameter, MediVault uses the date the order was placed as the default date. +> * Users cannot add orders if the order quantity exceeds maximum stock quantity. + +The sequence diagram for `AddOrderCommand` is shown below. + +![AddOrderCommandDiagram](diagrams/diagram_images/AddOrderSequenceDiagram.png) + +`addDate()` method adds the order date based on whether the user provided the date parameter or not. + +`addOrder()` method adds the order based on user input. + +MediVault determines if there exist the medication with the same name in order and in stock. + +* If there exist medication with the same name in order and in stock, MediVault checks if the `orderQuantity + +existingStockQuantity + existingOrderQuantity <= maxQuantity` to ensure total order quantity does not exceed the +existing maximum stock quantity allowed. +* If there exist medication with the same name in order but not in stock, MediVault checks if the `orderQuantity +< maxQuantity`, where `maxQuantity = Integer.MAX_VALUE` to allow the user to add any quantity of medication. +* If there does not exist medication with the same name in order but exist in stock, MediVault checks if the +`orderQuantity < existingStockQuantity` to ensure total order quantity does not exceed the existing maximum stock +quantity allowed. +* If there does not exist medication with the same name in order and in stock, MediVault does not check for valid quantity and simply add the order as a new order. + +#### DeleteOrderCommand + +MediVault creates a `DeleteOrderCommand` object when `CommandParser` identifies `deleteorder` or `delete` in `order` +mode. + +> :information_source: Note: +> * MediVault deletes the order when the `parameter` and `parameterValues` provided by the user are valid. + +The sequence diagram for `DeleteOrderCommand` is shown below. + +![DeleteOrderCommandDiagram](diagrams/diagram_images/DeleteOrderSequenceDiagram.png) + +- `isValidOrderParameters()` helps to ensure that the parameters for the order to be deleted are valid. + +#### UpdateOrderCommand + +MediVault creates an `UpdateOrderCommand` object when `CommandParser` identifies +`updateorder` or the `update` keyword in `order` mode. + +The sequence diagram for `UpdateOrderCommand` is shown below. + +![UpdateOrderSequenceDiagram](diagrams/diagram_images/UpdateOrderSequenceDiagram.png) + +MediVault retrieves the order object using the `i/ID` parameter specified by the user using the +`extractOrderObject()` method. + +> :warning: Warning: +> * MediVault disables updating an order that has been delivered. Users can only update information for pending orders. + +MediVault conducts a check if an order quantity is valid with the provided `q/QUANTITY`. +This validation check is separated from the initial validation checker as enforcing `q/QUANTITY <= m/MAX_QUANTITY` in +stocks can only be done **after** MediVault confirms that the user provides a `q/QUANTITY` is an integer. + +MediVault updates the order information only when all of the validation checks stated above are successful. + +#### ReceiveOrderCommand + +MediVault creates an `ReceiveOrderCommand` object when `CommandParser` identifies +`receiveorder` or the `receive` keyword in `order` mode. + +> :information_source: Note: +> * MediVault adds the order to stock if the `parameters` and `parameterValues` provided by the user are valid. +> * `ReceiveOrderCommand` calls `AddStockCommand` once the `parameters` and `parameterValues` are validated. +> * If the order contains a medication already in stock, the `d/DESCRIPTION` and `m/MAX_QUANTITY` are ignored +> and existing values are used. +> * If the `e/EXPIRY_DATE` provided is the same as the one in stock, `p/PRICE` is ignored as well. + +The sequence diagram for `ReceiveOrderCommand` is shown below. + +![ReceiveOrderSequenceDiagram](diagrams/diagram_images/ReceiveOrderSequenceDiagram.png) + +- `isStockParametersValid()` helps to ensure that the parameters for the stock to be added are valid. +- `checkStockExist()` helps to check if a medication exists in stock. + +MediVault then checks if the quantity increased before setting the order as completed. This helps to ensure that +only after the stock is successfully added, then the order would be complete. + +### Archive Commands + +#### ArchivePrescriptionCommand + +MediVault creates an `ArchivePrescriptionCommand` object when `CommandParser` identifies `archiveprescription` or the +`archive` keyword in `prescription` mode. + +* MediVault archives prescription records by specifying a date through `d/DATE`. +* MediVault removes prescription records that have date before or equals to the specified date and output it into +the file named `data/prescription_archive.txt` + +> :information_source: Note: +> * MediVault archive prescription information when `parameter` and `parameterValues` provided by the user are valid. +> * MediVault outputs the prescription information into a user readable format in `data/prescription_archive.txt`. +> * To modify the format, edit the code in `toArchiveFormat()` method in the Prescription Class. + +The sequence diagram for ArchivePrescriptionCommand is shown below. + +![ArchivePrescriptionSequenceDiagram](diagrams/diagram_images/ArchivePrescriptionSequenceDiagram.png) + +* `stringToDate()` helps to parse a string to a Date object. +* `prescriptionsToArchive()` checks through all prescriptions and look for records that have prescription date before or +equals to the specified date. +* `removeFromPrescriptions()` removes prescriptions from prescription list after archive. + +#### ArchiveOrderCommand + +MediVault creates an `ArchiveOrderCommand` object when `CommandParser` identifies `archiveorder` or the +`archive` keyword in `order` mode. + +* MediVault archives order records by specifying a date through `d/DATE`. +* MediVault removes only DELIVERED order records that have date before or equals to the specified date and output it +into the file named `data/order_archive.txt` + +> :information_source: Note: +> * MediVault archive order information when `parameter` and `parameterValues` provided by the user are valid. +> * MediVault outputs the order information into a user readable format in `data/order_archive.txt`. +> * To modify the format, edit the code in `toArchiveFormat()` method in the Order Class. + +The sequence diagram for ArchiveOrderCommand is shown below. + +![ArchiveOrderSequenceDiagram](diagrams/diagram_images/ArchiveOrderSequenceDiagram.png) + +* `stringToDate()` helps to parse a string to a Date object. +* `ordersToArchive()` checks through all orders and look for records that are DELIVERED and have order date +before or equals to the specified date. +* `removeFromOrders()` removes orders from order list after archive. + +## Product Scope + +### Target User Profile + +* Handles storing, ordering and prescribing of medication. +* Has a need to manage large number of stocks in the pharmacy. +* May forget how much medicine stock is left in the pharmacy. +* Is a fast typist. + +### Value Proposition + +The main value proposition of MediVault is such that it provides the user with an interface for efficient stock taking +purposes. It eradicates the need for manual tracking of medications which greatly lessen the administrative +workload of a pharmacist. It automates stock taking process to a certain extent because it is a 3 in 1 integrated +solution that provides real-time tracking of stock, prescriptions and orders in a pharmacy. ## User Stories |Version| As a ... | I want to ... | So that I can ...| |--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +|v1.0|pharmacist|list out all of the medicines currently on shelf| know the current quantity of the medicines on shelf +|v1.0|manager| purge all data|start afresh +|v1.0|user| exit the program|shutdown my computer +|v1.0|pharmacist|list the price of each medication|know the price of each medication +|v1.0|pharmacist|sort medication by price|recommend the customer the cheapest one if he asks +|v1.0|pharmacist| update medication information| modify information using a single command instead of deleting and adding the updated medication information +|v1.0|pharmacist|update the limit of a medication|have enough stocks in the event that I foresee a surge in demand +|v1.0|pharmacist|add new types of medicines| keep track of all the medication supplies on 1 platform +|v1.0|pharmacist|delete a medicine|remove it from the system in the event of a product recall or end of production +|v1.0|pharmacist|search for medication for specific symptoms|give the right medication +|v1.0|pharmacist|set a limit on the number of medications to be added to the stocks|prevent an oversupply of medication +|v1.0|user|search for a specific medication|look for a medication without looking through the full list +|v2.0|pharmacist|check which medication is expiring soon|order a new batch of supplies in time for my patients +|v2.0|pharmacist| check which medication is low in quantity| order a new batch of supplies in time for my patients +|v2.0|pharmacist|confirm a received order|know if an order is successfully received +|v2.0|manager|create orders|order medication. +|v2.0|pharmacist|archive past prescription records|prevent records from being cluttered +|v2.0|pharmacist| archive past order records|prevent records from being cluttered +|v2.0|pharmacist| list all orders|keep track of them +|v2.0|pharmacist|know the status of order| know whether the supply is ordered or received. +|v2.0|pharmacist|saved record of the current medicine stock| have a saved file record to refer to +|v2.0|pharmacist|saved record of the current medicine orders| have a saved file record to refer to +|v2.0|manager|saved record of the current medicine prescription| have a saved file record to refer to +|v2.0|pharmacist|prescribe medication|tally the number of medications when I prescribed some to my patients +|v2.0|pharmacist|delete prescription| delete a prescription record +|v2.0|manager|delete orders|cancel orders +|v2.0|pharmacist| update prescription information| modify information using a single command instead of deleting and adding the updated prescription information +|v2.0|pharmacist|delete ALL expired medications|expired medications will not be sold to customers or patients +|v2.0|manager|edit orders| update any wrong information +|v2.0|manager|see the pending orders to reflect in my current stocks|ensure that I won't double order on the same medication +|v2.0|pharmacist|search for records by a specific customer|see all his prescriptions +|v2.0|manager|check who prescribe what medication|know who is responsible for the prescription ## Non-Functional Requirements -{Give non-functional requirements} +* **Accessibility Requirements:** MediVault should be able to run locally without internet connection. +* **Capacity Requirements:** MediVault should try to store only important details to minimize data file size as there may be +many data records after long usage. Perhaps could save into multiple files or archive data. +* **Compliance with regulations requirements:** MediVault should comply with regulations related to storing of sensitive +customer information. +* **Documentation Requirements:** MediVault user guide should be documented in a way that a pharmacist without CLI +experience can understand and learn how to use the application. +* **Efficiency Requirements:** MediVault should make use of efficient data structures and algorithms where appropriate to +optimise speed if possible. However, it is not really a top priority. +* **Extensibility Requirements:** MediVault should minimally manage medications. In the future can probably expand inventory +to handle medical supplies in general. +* **Fault Tolerance Requirements:** MediVault should perform sufficient error handling and provide helpful error response +messages to suggest correct input to user. +* **Interoperability Requirements:** MediVault should be able to run on minimally Windows, Linux and macOS. +* **Privacy Requirements:** MediVault may contain sensitive information such as customer health records. Thus, we should not +publish our data to the internet and only store it on our local computer. +* **Portability Requirements:** MediVault should be able to run on any computer that has Java 11 and `MediVault.jar`. Data +should also be portable such that we can easily transfer data when changing computers. +* **Reliability Requirements:** MediVault should not crash at any point in time. Even if it does, it must retain data. +* **Response Time Requirements:** MediVault basic operations should respond within 3 seconds. For other processing heavy +operations such as start up and loading of data, it should respond within maximum of 15 seconds. +* **Robustness Requirements:** MediVault should have some had some testing done be it JUnit Tests or automated I/O +redirection tests. +* **Scalability Requirements:** MediVault should be built to handle amount of data a small to medium enterprise would have. +* **Stability Requirements:** MediVault should function as per normal regardless of how many error user has made. +* **User Requirements:** MediVault should be user-friendly such that it is usable by a pharmacist with no CLI experience. -## Glossary -* *glossary item* - Definition +## Instructions for Manual Testing + +### Starting up and Shutting Down + +1. Download the latest release [here](https://github.com/AY2122S1-CS2113T-T10-1/tp/releases). +2. Run MediVault using `java -jar MediVault.jar` +3. To end program, enter the command `exit`. + +### Running commands + +1. You can refer to the list of commands and expected + outputs [here](https://ay2122s1-cs2113t-t10-1.github.io/tp/UserGuide.html). + +### Saving Data + +All data files are located in the `data` folder. + +1. Data is saved in `stock.txt`, `prescription.txt`, `order.txt`. + * Test Case: + 1. Run the application. + 2. Add an entry to stock, prescription and order into MediVault. + 3. Exit MediVault. The `stock.txt`, `prescription.txt` and `order.txt` will have one entry. + 4. Run the application. + 5. Delete entry to stock, prescription and order into MediVault. + 6. Exit MediVault. + * Expected: `stock.txt`, `prescription.txt` and `order.txt` will be empty. +2. Archive data is saved in `order_archive.txt` and `prescription_archive.txt`. + * Test Case: + 1. Run the application. + 2. Add entries to prescription and order into MediVault. + 3. Run the `archiveorder` and `archiveprescription` command with date specified. + 4. Exit MediVault. + * Expected: `order_archive.txt` and `prescription_archive.txt` will have entries. + +## Instructions for Automated Testing + +### Gradle Build Tests + +MediVault uses Gradle for Continuous Integration during development. Gradle performs automated checking of +coding style which helps in ensuring adherence to Java Coding Standards. Gradle also helps to automate testing by running +our JUnit test cases to ensure that MediVault is bug-free based on our testing and is working as intended. It is helpful +in catching unintended bugs while we continuously develop MediVault. + +### JUnit Tests -## Instructions for manual testing +> :bulb: Tip +> * **Equivalence Partitions:** Create Effective and Efficient Test Cases by considering Equivalence Partitions +> * **Boundary Value Analysis:** Focus on testing Boundary Values -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +To contribute and develop JUnit Test Cases: +1. Locate `tp/src/test/java/` folder. +2. Decide which aspect of MediVault you will be creating JUnit Tests for. + * Command, Parsers, Validators, etc. +3. Start coding JUnit Test Cases in the appropriate files. + * Aim to create both Positive and Negative test cases. diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..28f96d1518 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,10 @@ -# Duke +# MediVault -{Give product intro here} +MediVault is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. +It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. Useful links: + * [User Guide](UserGuide.md) * [Developer Guide](DeveloperGuide.md) * [About Us](AboutUs.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..91aeec3fc9 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,41 +2,1018 @@ ## Introduction -{Give a product intro} +MediVault is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. +It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. -## Quick Start +## Contents -{Give steps to get started quickly} +* [Purpose](#purpose) +* [Glossary](#glossary) +* [Quick Start](#quick-start) + * [Setting up](#setting-up) + * [Changing modes](#changing-modes) +* [Features](#features) + * [Managing Stocks](#managing-stocks) + * [Adding stocks](#adding-stocks-addstock) + * [Listing stocks](#listing-medication-stocks-liststock) + * [Updating stocks](#updating-stocks-updatestock) + * [Deleting stocks](#deleting-a-medication-stock-deletestock) + * [Managing Prescriptions](#managing-prescriptions) + * [Adding prescriptions](#adding-prescriptions-addprescription) + * [Listing prescriptions](#listing-prescriptions-listprescription) + * [Updating prescriptions](#updating-prescriptions-updateprescription) + * [Deleting prescriptions](#deleting-prescriptions-deleteprescription) + * [Managing Orders](#managing-orders) + * [Adding orders](#adding-an-order-addorder) + * [Listing orders](#listing-orders-listorder) + * [Updating orders](#updating-orders-updateorder) + * [Deleting orders](#deleting-an-order-deleteorder) + * [Receiving orders](#receiving-orders-receiveorder) + * [Managing Data](#managing-data) + * [Archive orders](#archive-orders-archiveorder) + * [Archive prescriptions](#archive-prescriptions-archiveprescription) + * [Purge data](#purging-existing-data--purge) + * [Miscellaneous](#miscellaneous) + * [Help](#showing-help-page--help) + * [Exit](#exiting-medivault--exit) +* [Data Handling](#data-handling) + * [Data Storage](#data-storage) + * [Data Editing](#data-editing) +* [FAQ](#faq) +* [Command Summary](#command-summary) -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +# Purpose -## Features +The purpose of this user guide is for users to have a more detailed understanding and reference to usage of commands in +MediVault. In this user guide, you can expect to find examples and expected outputs of each command. MediVault caters +for normal users and advanced users so you can expect to find information about how to efficiently use the commands or +make use of the mode switching capabilities for convenience. -{Give detailed description of each feature} +As a pharmacist, you would probably focus more on the sections related to stock and prescriptions. As a manager of the +pharmacy, you may be more interested in the order and data management sections of the user guide. -### Adding a todo: `todo` -Adds a new item to the list of todo items. +# Glossary -Format: `todo n/TODO_NAME d/DEADLINE` +Terminology | Meaning +------ | ------ +Stock | Refers to a medication. +Prescription | Refers to a prescription. +Order | Refers to ordering new medications to replenish the stocks. +Parameters | Prefixes for MediVault to understand the type of information you provide. Parameters must be specified with a `/`. For example `list sort/n` is considered a valid parameter whereas `list sort` is not a valid parameter. -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Meaning of Icons: +- :information_source: Note +- :warning: Warning +- :bulb: Tip -Example of usage: +# Quick Start -`todo n/Write the rest of the User Guide d/next week` +### Setting up -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +1. Ensure that you have **Java 11** or above installed. +2. Download the latest version of `MediVault.jar` + from [here](https://github.com/AY2122S1-CS2113T-T10-1/tp/releases/download/v2.1/MediVault.jar). +3. Copy the file to the folder you want to use as the **home** folder for `MediVault`. +4. In the terminal, run `java -jar MediVault.jar`. +5. You should see the following prompt if the program has started successfully. + +``` +| \/ | | |(_)| | | | | || | +| . . | ___ __| | _ | | | | __ _ _ _ | || |_ +| |\/| | / _ \ / _` || || | | | / _` || | | || || __| +| | | || __/| (_| || |\ \_/ /| (_| || |_| || || |_ +\_| |_/ \___| \__,_||_| \___/ \__,_| \__,_||_| \__| +Welcome to MediVault! +[STOCK] > +``` +> :bulb: Tip: MediVault is best used when the window is maximised to ensure that all tables are rendered correctly. + + +### Changing Modes + +> :bulb: Tip: Advanced users can choose to omit mode selection to get things done faster. + +MediVault includes a mode feature to make the commands simpler for you. Your current mode is indicated in the square +brackets on the bottom left of the console `[STOCK] >`. It allows you to type `add`, `list`, `update`, `delete` without +typing in the full command. Additionally, you can type `archive` in both `prescription` and `order` mode and `receive` +in `order` mode. For example, when you are in `order` mode, typing `list` is equivalent to `listorder`. + +Type `stock`, `prescription` or `order` to change to the respective modes. + +Example (Current mode is Stock): + +Expected Output: + +``` +[STOCK] > listorder ++====+=========+==========+============+=========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+=========+==========+============+=========+ +| 1 | PANADOL | 100 | 09-10-2021 | PENDING | ++----+---------+----------+------------+---------+ +| 2 | VICODIN | 30 | 09-10-2021 | PENDING | ++----+---------+----------+------------+---------+ +[STOCK] > order +Mode has changed to ORDER. +[ORDER] > list ++====+=========+==========+============+=========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+=========+==========+============+=========+ +| 1 | PANADOL | 100 | 09-10-2021 | PENDING | ++----+---------+----------+------------+---------+ +| 2 | VICODIN | 30 | 09-10-2021 | PENDING | ++----+---------+----------+------------+---------+ +``` + +# Features + +> :information_source: Notes about the commands: +> +> You can refer to [Glossary](#glossary) to understand technical terms mentioned below. +> * Parameters enclosed in `[]` should contain **one or more** optional parameters. +> * Parameters enclosed in `{}` are **totally** optional parameters. +> * Parameters enclosed in `()` are **conditional** optional parameters. For `addstock` and `receiveorder`, parameters +> `d/DESCRIPTION` and `m/MAX_QUANTITY` will be optional only if the stock exists. +> * Parameters you specify can be in any order. +> * E.g. `update i/1 q/100 m/200` and `update i/1 m/200 q/100` are both acceptable. +> * MediVault ignores additional parameters provided when commands do not require one. +> * If you specify the same parameter multiple times, MediVault will accept the last occurrence. +> * E.g. `delete i/2 i/1`, MediVault interprets the command as `delete i/1`. +> * MediVault also ignores all extra values that are not provided in parameters. +> * E.g. `list abc123 i/1`, MediVault interprets the command as `list i/1`. +> * MediVault's commands are case-insensitive. +> * Dates in the `d/DATE` and `e/EXPIRY_DATE` field are in `DD-MM-YYYY` format. +> * Column names in the `sort` parameter can be provided as the full column name or the column alias. +> * E.g. `NAME` is equivalent to `n` and `QUANTITY` is equivalent to `q`. +> * For the `list` commands, use the `sort` parameter to sort by a column in ascending order and `rsort` parameter to +> sort in descending order. +> * For the `delete` commands, ID will not reset after deletion as stock ID, order ID and prescription ID are unique so that MediVault +> can identify each stock, order and prescription entry uniquely. +> * `/` is not allowed to be entered for all input parameters, as MediVault uses it to identity the parameters. + +## Managing Stocks + +### Adding stocks: `addstock` + +Adds medication into the inventory. + +> :warning: Warning: +> * If medication exists, description and maximum quantity will be optional parameters. If you include `d/DESCRIPTION` or `m/MAX_QUANTITY` parameter, it will be ignored and MediVault will add the medication with the existing description and existing maximum quantity. +> * If medication and expiry date exists, price, description and maximum quantity will be optional parameters. If you include `p/PRICE` or `d/DESCRIPTION` or `m/MAX_QUANTITY` parameter, it will be ignored and MediVault will add the medication with the existing price, existing description and existing maximum quantity. + +> :information_source: Note: +> * Description will be standardised in the stock so that it will be easier for users to search for medication by the symptoms. +> * Users will be able to search for medication by symptoms using the list command as shown below. +> * Expiry date is compulsory so that user will be able to track the expiry date for all medication. +> * Users will be able to view all the expired medication or expiring medication easily using the list command as shown below. +> * Medication with same name but different expiry date will be added into MediVault in different rows. This will allow users to have different prices for different expiry dates. +> * Users might want to set a discount price for expiring medication. + +Format: `addstock n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE (d/DESCRIPTION m/MAX_QUANTITY)` + +Example 1 (If medication exists): `addstock n/panadol p/5 q/50 e/19-09-2025` + +Expected Output 1: + +``` +Medicine exists. Using existing description and maximum quantity. +Medication added: PANADOL ++====+=========+=======+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+=======+==========+=============+=============+==============+ +| 7 | PANADOL | $5.00 | 50 | 19-09-2025 | HEADACHES | 1000 | ++----+---------+-------+----------+-------------+-------------+--------------+ +``` + +Example 2 (If medication and expiry date exists): `addstock n/panadol q/50 e/19-09-2025` + +Expected Output 2: + +``` +Same Medication and Expiry Date exist. Using existing price, description and +maximum quantity. Updating existing quantity. ++====+=========+=======+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+=======+==========+=============+=============+==============+ +| 8 | PANADOL | $5.00 | 100 | 19-09-2025 | HEADACHES | 1000 | ++----+---------+-------+----------+-------------+-------------+--------------+ +``` + +Example 3 (If medication does not exist): `addstock n/paracetamol q/10 p/10 e/02-11-2021 d/fever m/500` + +Expected Output 3: + +``` +Medication added: PARACETAMOL ++====+=============+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=============+========+==========+=============+=============+==============+ +| 9 | PARACETAMOL | $10.00 | 10 | 02-11-2021 | FEVER | 500 | ++----+-------------+--------+----------+-------------+-------------+--------------+ +``` + +### Listing medication stocks: `liststock` + +Lists all existing medications in the inventory. + +* All parameters for `liststock` command are optional, you can choose to list medication by any of the parameters. +* You are able to `liststock` by any column and sort or reverse sort them. +* When you update an order information, MediVault reflects the pending stocks shown here. +* The total pending quantity will be shown if there are orders for a medication. +* `low/LESS_THAN_OR_EQUAL_QUANTITY` and `expiring/LESS_THAN_OR_EQUAL_EXPIRY_DATE` parameters can be used to search for +stocks with low **total** remaining quantity and expiring stocks respectively. + +Format: `liststock {i/ID n/NAME p/PRICE q/QUANTITY low/LESS_THAN_OR_EQUAL_QUANTITY e/EXPIRY_DATE expiring/LESS_THAN_OR_EQUAL_EXPIRY_DATE d/DESCRIPTION m/MAX_QUANTITY sort/COLUMN_NAME rsort/COLUMN_NAME}` + +Example 1 (Listing all medications): `liststock` + +Expected Output 1: + +``` ++====+============+========+==========+=============+================+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+============+========+==========+=============+================+==============+ +| 1 | PANADOL | $20.00 | 20 | 13-09-2021 | HEADACHES | 1000 | ++----+------------+--------+----------+-------------+----------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | HEADACHES | 1000 | ++----+------------+--------+----------+-------------+----------------+--------------+ +| 3 | VICODIN | $10.00 | 20 | 30-09-2021 | SEVERE PAIN | 500 | ++----+------------+--------+----------+-------------+----------------+--------------+ +| 4 | LISINOPRIL | $20.00 | 25 | 15-10-2021 | HYPOTHYROIDISM | 800 | ++----+------------+--------+----------+-------------+----------------+--------------+ +``` + +Example 2 (Filter by name): `liststock n/panadol` + +Expected Output 2: + +``` ++====+=========+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==========+=============+=============+==============+ +| 1 | PANADOL | $20.00 | 20 | 13-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +``` + +### Updating stocks: `updatestock` + +Updates existing medication stock information in the inventory. + +> :warning: Warning: +> * The Stock ID must exist in MediVault. +> * You cannot update the Stock ID. +> * The allocation of Stock ID is determined by MediVault. +> * If you include the `n/NAME`, `d/DESCRIPTION` or `m/MAX_QUANTITY` parameter, MediVault updates **all** entries that +has same existing medication name given the `i/ID` with your input values for these parameters. + +> :information_source: Note: +> * A new Stock ID will be assigned to the current stock if your update has the `n/NAME` parameter. +> * When you update the `n/NAME` parameter, there may be an existing prescription record that is present. +> * By allocating a new Stock ID to the updated stock record, MediVault preserves the name of the old record so that +when you delete a prescription record, it is **guaranteed** to automatically update the quantity of the stock. + +Format: `updatestock i/ID [n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE d/DESCRIPTION m/MAX_QUANTITY]` + +Initial stock records: + +``` ++====+=========+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==========+=============+=============+==============+ +| 1 | PANADOL | $20.00 | 20 | 13-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 3 | VICODIN | $10.00 | 20 | 30-09-2021 | SEVERE PAIN | 500 | ++----+---------+--------+----------+-------------+-------------+--------------+ +``` + +> :information_source: Note: +> * Examples stated below are **independent** from each other. + +Example 1 (Updating with medication name present): +`update i/3 n/amoxil p/20 q/50 e/01-12-2021 d/body infections m/100` + +Expected Output 1: + +``` +Updated! Number of rows affected: 1 +Stock Id changed from: +3 -> 4 ++====+========+========+==========+=============+=================+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+========+========+==========+=============+=================+==============+ +| 4 | AMOXIL | $20.00 | 50 | 01-12-2021 | BODY INFECTIONS | 100 | ++----+--------+--------+----------+-------------+-----------------+--------------+ +``` + +Example 2 (Updating only price and description): +`update i/1 p/30 d/fever` + +Expected Output 2: + +``` +Updated! Number of rows affected: 2 ++====+=========+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==========+=============+=============+==============+ +| 1 | PANADOL | $30.00 | 20 | 13-09-2021 | FEVER | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | FEVER | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +``` + +### Deleting a medication stock: `deletestock` + +If you made a mistake and want to delete a medication from the inventory, you may do so by using this command. + +* Able to delete a specific stock by specifying Stock ID using `i/ID`. +* Able to delete multiple stocks that have expiry date before and equals to specified date using `expiring/EXPIRY_DATE`. + +Format: `deletestock [i/ID expiring/EXPIRY_DATE]` + +Example 1 (Deletion by Stock ID): `deletestock i/3` + +Expected Output 1: + +``` +Deleted row with Stock Id: 3 +``` + +Example 2 (Deletion by expiry date): `deletestock expiring/10-10-2021` + +Expected Output 2: + +``` +Deleted expired medications! Rows deleted: 4 +``` + +## Managing Prescriptions + +### Adding prescriptions: `addprescription` + +Adds a prescription record and subtracts the medication quantity from stocks. + +> :information_source: Note: +> * MediVault will prescribe the medication with the shortest expiry date first. +If the remaining quantity of the current batch of medication is insufficient, the next batch of medication will be used to supplement the prescription. +> * MediVault will add another entry to prescription even if the medication name, customer's ID, date and staff name is exactly the same. +This is so that MediVault can track all entries. +> * If users want to increase the quantity of medication prescribed, users can use `updateprescription` command instead. +> * MediVault will set the prescription date as today's date. + + +Format: `addprescription n/NAME q/QUANTITY s/STAFF c/CUSTOMER_ID` + +Example: `addprescription n/panadol q/5 s/john c/123` + +Expected Output: + +``` +Prescribed:PANADOL Quantity:5 Expiry Date:14-09-2021 ++====+=========+==========+=============+============+=======+==========+ +| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | ++====+=========+==========+=============+============+=======+==========+ +| 9 | PANADOL | 5 | 123 | 26-10-2021 | JOHN | 2 | ++----+---------+----------+-------------+------------+-------+----------+ +``` + +### Listing prescriptions: `listprescription` + +Lists all prescription records in the application. + +* All parameters for `listprescription` command are optional, you can choose to list the records by any of the + parameters. +* You are able to `listprescription` by any column and sort or reverse sort them. + +Format: `listprescription {i/ID n/NAME q/QUANTITY c/CUSTOMER_ID d/DATE s/STAFF_NAME sid/STOCK_ID sort/COLUMN_NAME rsort/COLUMN_NAME}` + +Example 1 (Listing all prescriptions): `listprescription` + +Expected Output 1: + +``` ++====+==============+==========+=============+============+=======+==========+ +| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | ++====+==============+==========+=============+============+=======+==========+ +| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 2 | VICODIN | 10 | S2345678B | 10-10-2021 | PETER | 3 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 3 | SIMVASTATIN | 10 | S1234567A | 11-10-2021 | SAM | 4 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 5 | AZITHROMYCIN | 10 | S2345678B | 13-10-2021 | PETER | 6 | ++----+--------------+----------+-------------+------------+-------+----------+ +``` + +Example 2 (Listing prescriptions sorted by staff): `listprescription sort/s` + +Expected Output 2: + +``` ++====+==============+==========+=============+============+=======+==========+ +| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | ++====+==============+==========+=============+============+=======+==========+ +| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 2 | VICODIN | 10 | S2345678B | 10-10-2021 | PETER | 3 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 5 | AZITHROMYCIN | 10 | S2345678B | 13-10-2021 | PETER | 6 | ++----+--------------+----------+-------------+------------+-------+----------+ +| 3 | SIMVASTATIN | 10 | S1234567A | 11-10-2021 | SAM | 4 | ++----+--------------+----------+-------------+------------+-------+----------+ +``` + +### Updating prescriptions: `updateprescription` + +Updates an existing prescription information. + +> :warning: Warning: +> * You **cannot** update the Stock or the Prescription ID. +> * The allocation of Prescription ID is determined by MediVault. +> * Your provided `n/NAME` parameter **must** exist in stocks. +> * When you update a prescription record, the stock information may be affected as well. +> * MediVault does not combine prescription information even if the column information are the same. +> * You **cannot** update an existing prescription information with 0 quantity. You must use the `deleteprescription` +command instead. +> * You **cannot** update the prescription date with a date after today. + +> :information_source: Note: +> * MediVault allocates a **new** Prescription ID when you update prescription records containing the `n/NAME` and +`q/QUANTITY` parameter. +> * This is because MediVault deletes the old prescription record and adds the updated prescription record as a **new** +prescription record. + +Format: `updateprescription i/ID [n/NAME q/QUANTITY c/CUSTOMER_ID d/DATE s/STAFF_NAME]` + +Initial stock records: + +``` ++====+=========+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==========+=============+=============+==============+ +| 1 | PANADOL | $20.00 | 20 | 13-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 3 | VICODIN | $10.00 | 20 | 30-09-2021 | SEVERE PAIN | 500 | ++----+---------+--------+----------+-------------+-------------+--------------+ +``` + +Initial prescription records: + +``` ++====+=========+==========+=============+============+=======+==========+ +| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | ++====+=========+==========+=============+============+=======+==========+ +| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | ++----+---------+----------+-------------+------------+-------+----------+ +| 2 | VICODIN | 10 | S2345678B | 10-10-2021 | PETER | 3 | ++----+---------+----------+-------------+------------+-------+----------+ +``` + +> :information_source: Note: +> * Examples stated below are **independent** from each other. + +Example 1 (Update prescribed quantity): +`updateprescription i/1 q/5` + +Expected Output 1: + +``` +Restored 5 PANADOL +Updated prescription information! ++====+=========+==========+=============+============+=======+==========+ +| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | ++====+=========+==========+=============+============+=======+==========+ +| 3 | PANADOL | 5 | S1234567A | 09-10-2021 | JANE | 1 | ++----+---------+----------+-------------+------------+-------+----------+ +``` + +Updated stock record for Example 1: + +``` ++====+=========+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==========+=============+=============+==============+ +| 1 | PANADOL | $20.00 | 25 | 13-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 3 | VICODIN | $10.00 | 20 | 30-09-2021 | SEVERE PAIN | 500 | ++----+---------+--------+----------+-------------+-------------+--------------+ +``` + +Example 2 (Update staff who prescribed the medication): +`updateprescription i/1 s/jack` + +Expected Output 2: + +``` +Updated prescription information! ++====+=========+==========+=============+============+=======+==========+ +| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | ++====+=========+==========+=============+============+=======+==========+ +| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JACK | 1 | ++----+---------+----------+-------------+------------+-------+----------+ +``` + +### Deleting prescriptions: `deleteprescription` + +If you made a mistake and want to delete a prescription record you may do so by using this command along with a specific +Prescription ID. + +> :information_source: Note: +> * Users will not be able to delete a prescription if deleting the prescription will lead to stock quantity exceeding the maximum quantity. + +Format: `deleteprescription i/PRESCRIPTION_ID` + +Example: `deleteprescription i/3` + +Expected Output: + +``` +Prescription deleted for Prescription ID 3 +``` + +## Managing Orders + +### Adding an order: `addorder` + +Adds an order for a stock. + +> :information_source: Note: +> * The date parameter is optional, MediVault will set it as the date you added in the order if the parameter is omitted. +> * If the order quantity exceeds the maximum stock quantity allowed, you are unable to add the order. +> * You **cannot** add an order date with a date after today. + +Format: `addorder n/NAME q/QUANTITY {d/DATE}` + +Initial stock records: +``` ++====+=========+========+==========+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==========+=============+=============+==============+ +| 1 | PANADOL | $20.00 | 20 | 13-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +| 2 | PANADOL | $20.00 | 10 | 14-09-2021 | HEADACHES | 1000 | ++----+---------+--------+----------+-------------+-------------+--------------+ +``` + +Example 1 (add order with date parameter): `addorder n/panadol q/150 d/21-10-2021` + +Expected Output 1: + +``` +Order added: PANADOL ++====+=========+==========+============+=========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+=========+==========+============+=========+ +| 1 | PANADOL | 150 | 21-10-2021 | PENDING | ++----+---------+----------+------------+---------+ +``` + +Example 2 (add order without date parameter): `addorder n/panadol q/100` +> :information_source: Note: +> This example was done on 5 Nov 2021. + +Expected Output 2: + +``` +Order added: PANADOL ++====+=========+==========+============+=========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+=========+==========+============+=========+ +| 2 | PANADOL | 100 | 05-11-2021 | PENDING | ++----+---------+----------+------------+---------+ +``` + +Example 3 (add order quantity exceeds maximum stock quantity allowed): `addorder n/panadol q/1000` + +Expected Output 3: +``` +Unable to add order as total order quantity exceeds maximum stock quantity of 1000. +Existing quantity in stock: 30 +Pending order quantity: 250 +``` + +### Listing orders: `listorder` + +Lists all order records in the application. + +* All parameters for `listorder` command are optional, you can choose to list the records by any of the parameters. +* You are able to `listorder` by any column and sort or reverse sort them. + +Format: `listorder {i/ID n/NAME q/QUANTITY d/DATE s/STATUS sort/COLUMN_NAME rsort/COLUMN_NAME}` + +Example 1 (List all orders): `listorder` + +Expected Output 1: + +``` ++====+==============+==========+============+===========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+==============+==========+============+===========+ +| 1 | PANADOL | 100 | 09-10-2021 | PENDING | ++----+--------------+----------+------------+-----------+ +| 2 | VICODIN | 30 | 09-10-2021 | PENDING | ++----+--------------+----------+------------+-----------+ +| 3 | VICODIN | 50 | 10-10-2021 | DELIVERED | ++----+--------------+----------+------------+-----------+ +| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | ++----+--------------+----------+------------+-----------+ +| 5 | LISINOPRIL | 200 | 12-10-2021 | PENDING | ++----+--------------+----------+------------+-----------+ +| 6 | AZITHROMYCIN | 100 | 13-10-2021 | PENDING | ++----+--------------+----------+------------+-----------+ +| 7 | PANADOL | 50 | 13-10-2021 | PENDING | ++----+--------------+----------+------------+-----------+ +``` + +Example 2 (List pending orders): `listorder s/pending` + +Expected Output 2: + +``` ++====+==============+==========+============+=========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+==============+==========+============+=========+ +| 1 | PANADOL | 100 | 09-10-2021 | PENDING | ++----+--------------+----------+------------+---------+ +| 2 | VICODIN | 30 | 09-10-2021 | PENDING | ++----+--------------+----------+------------+---------+ +| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | ++----+--------------+----------+------------+---------+ +| 5 | LISINOPRIL | 200 | 12-10-2021 | PENDING | ++----+--------------+----------+------------+---------+ +| 6 | AZITHROMYCIN | 100 | 13-10-2021 | PENDING | ++----+--------------+----------+------------+---------+ +| 7 | PANADOL | 50 | 13-10-2021 | PENDING | ++----+--------------+----------+------------+---------+ +``` + +### Updating orders: `updateorder` + +Updates an existing order information. + +> :warning: Warning: +> * You cannot update the Order ID or the status of the order. +> * The allocation of Order ID is determined by MediVault. +> * The status of the order will only be changed when you run the `receiveorder` command. +> * MediVault does not combine orders even if the column information are the same. +> * You **cannot** update the order date with a date after today. + +Format: `updateorder i/ID [n/NAME q/QUANTITY d/DATE]` + +Initial order records: + +``` ++====+=========+==========+============+===========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+=========+==========+============+===========+ +| 1 | PANADOL | 100 | 09-10-2021 | PENDING | ++----+---------+----------+------------+-----------+ +``` + +Example: `updateorder i/1 q/50 d/10-10-2021` + +Expected Output: + +``` +Updated! Number of rows affected: 1 ++====+=========+==========+============+=========+ +| ID | NAME | QUANTITY | DATE | STATUS | ++====+=========+==========+============+=========+ +| 1 | PANADOL | 50 | 10-10-2021 | PENDING | ++----+---------+----------+------------+---------+ +``` + +### Deleting an order: `deleteorder` + +If you made a mistake and want to delete an order, you may do so by using this command along with a specific Order ID. + +Format: `deleteorder i/ORDER_ID` + +Example: `deleteorder i/1` + +Expected Output: + +``` +Order deleted for Order ID 1 +``` + +### Receiving orders: `receiveorder` + +Adds the medication you ordered into the current stocks. + +> :information_source: Note: +> * Your input Order ID must exist. +> * When you run `receiveorder` with the required parameters, the medication you ordered will be automatically added into your current stocks. +> * The `e/EXPIRY_DATE` parameter is required so that MediVault knows the expiry date of the stock that just arrived. +> * The `p/PRICE` parameter is also required so that stocks with different remaining shelf life can have different prices. + +> :warning: Warning: +> * If medication exists, `d/DESCRIPTION` and `m/MAX_QUANTITY` will be optional parameters. If you include `d/DESCRIPTION` or `m/MAX_QUANTITY` parameter, it will be ignored and MediVault will add the medication with the existing description and existing maximum quantity in stocks. +> * If medication exists, the medication to be added has the same `e/EXPIRY_DATE`, the value in the `p/PRICE` parameter will be ignored and the existing price will be used. +> * You may not be able to `receiveorder` if the order `quantity + current stock quantity > max quantity`. + +Format: `receiveorder i/ID p/PRICE e/EXPIRY_DATE (d/DESCRIPTION m/MAX_QUANTITY)` + +Example 1 (If medication does not exist) : `receiveorder i/1 p/10 e/20-10-2021 d/severe pain m/500` + +Expected Output 1: + +``` +Medication added: VICODIN ++====+=========+========+=============+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+=============+=============+=============+==============+ +| 3 | VICODIN | $10.00 | 100 | 20-10-2021 | SEVERE PAIN | 500 | ++----+---------+--------+-------------+-------------+-------------+--------------+ +``` + +Example 2 (If medication exists) : `receiveorder i/2 p/20 e/25-10-2021` + +Expected Output 2: + +``` +Same Medication and Expiry Date exist. Using existing price, description and +maximum quantity. Updating existing quantity. ++====+=========+========+==============+=============+=============+==============+ +| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | ++====+=========+========+==============+=============+=============+==============+ +| 1 | PANADOL | $20.00 | 100 | 25-10-2021 | HEADACHE | 1000 | ++----+---------+--------+--------------+-------------+-------------+--------------+ +``` + +## Managing Data + +This section of the user guide explains the features and usage of commands related to data management. This includes the +archive commands, purge command as well as data storage files related information. + +### Archive orders: `archiveorder` + +Archive order records into data/order_archive.txt file. + +> :information_source: Note: +> * MediVault will remove all order records that have status of DELIVERED from the current orders that matches the user specified date and before. +> * MediVault will then archive it into data/order_archive.txt file. +> * The date parameter is compulsory. + +> :warning: Warning: +> * This is a one way command, there is no reversal except for you to manually add the archived records back into MediVault. + +Format: `archiveorder d/DATE` + +Example: `archiveorder d/10-10-2021` + +Expected Output: + +``` +Archived 2 delivered orders from 10-10-2021 +``` + +Expected Output (in data/order_archive.txt): + +``` +[ORDER ID: 2] 10 PANADOL WAS ORDERED ON 09-10-2021. STATUS: DELIVERED +[ORDER ID: 3] 50 VICODIN WAS ORDERED ON 10-10-2021. STATUS: DELIVERED +``` + +### Archive prescriptions: `archiveprescription` + +Archive prescription records into data/prescription_archive.txt file. + +> :information_source: Note: +> * MediVault will remove all prescription records from the current prescriptions that matches the user specified date and before. +> * MediVault will then archive it into data/prescription_archive.txt file. +> * The date parameter for this command is compulsory. + +> :warning: Warning: +> * This is a one way command, there is no reversal except for you to manually add the archived records back into MediVault. + +Format: `archiveprescription d/DATE` + +Example: `archiveprescription d/10-10-2021` + +Expected Output: + +``` +Archived 2 prescriptions from 10-10-2021 +``` + +Expected Output (in data/prescription_archive.txt): + +``` +[PRESCRIPTION ID: 1] 1 AXID [STOCK ID: 8] WAS PRESCRIBED BY CJ TO S1234567A ON 09-10-2021 +[PRESCRIPTION ID: 2] 1 AZOR [STOCK ID: 9] WAS PRESCRIBED BY AJ TO S2345678B ON 10-10-2021 +``` + +### Purging existing data : `purge` + +Should you want to clear all data in MediVault, you can use the `purge` command. + +Format: `purge` + +Example: `purge` + +Expected Output: + +``` +Are you sure you want to delete all data? (Y/N) +Y +All data has been cleared! +``` + +## Miscellaneous + +### Showing help page : `help` + +Should you require assistance on how to use MediVault, you can use the `help` command. + +Format:`help` + +Example: `help` + +Expected Output: + +``` +Welcome to the help page. +Your current mode is indicated in the square brackets at the bottom left of the console. +It allows you to type add, list, update, delete without typing in the full command. +Additionally, you can type archive in both prescription and order mode and receive in +order mode. +Type stock, prescription or order to change to respective modes. +Note that parameters in {curly braces} are optional. +Parameters in [square braces] indicate that at least one of the parameter(s) must be +provided. +Parameters enclosed in (round brackets) are conditional optional parameters. +For example, the parameters d/DESCRIPTION and m/MAX_QUANTITY in addstock and +receiveorder will be optional only if the stock exists. ++=====================+====================================================+ +| COMMAND | COMMAND SYNTAX | ++=====================+====================================================+ +| addstock | addstock n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE | +| | (d/DESCRIPTION m/MAX_QUANTITY) | ++---------------------+----------------------------------------------------+ +| deletestock | deletestock [i/ID expiring/EXPIRY_DATE] | ++---------------------+----------------------------------------------------+ +| updatestock | updatestock i/ID [n/NAME p/PRICE q/QUANTITY | +| | e/EXPIRY_DATE d/DESCRIPTION m/MAX_QUANTITY] | ++---------------------+----------------------------------------------------+ +| liststock | liststock {i/ID n/NAME p/PRICE | +| | q/QUANTITYlow/LESS_THAN_OR_EQUAL_QUANTITY | +| | e/EXPIRY_DATE | +| | expiring/LESS_THAN_OR_EQUAL_EXPIRY_DATE | +| | d/DESCRIPTION m/MAX_QUANTITY sort/COLUMN_NAME | +| | rsort/COLUMN_NAME} | ++---------------------+----------------------------------------------------+ +| addprescription | addprescription n/NAME q/QUANTITY c/CUSTOMER_ID | +| | s/STAFF_NAME | ++---------------------+----------------------------------------------------+ +| deleteprescription | deleteprescription i/ID | ++---------------------+----------------------------------------------------+ +| updateprescription | updateprescription i/ID [n/NAME q/QUANTITY | +| | c/CUSTOMER_ID d/DATE s/STAFF_NAME] | ++---------------------+----------------------------------------------------+ +| listprescription | listprescription {i/ID n/NAME q/QUANTITY | +| | c/CUSTOMER_ID d/DATE s/STAFF_NAME sid/STOCK_ID | +| | sort/COLUMN_NAME rsort/COLUMN_NAME} | ++---------------------+----------------------------------------------------+ +| archiveprescription | archiveprescription d/DATE | ++---------------------+----------------------------------------------------+ +| addorder | addorder n/NAME q/QUANTITY {d/DATE} | ++---------------------+----------------------------------------------------+ +| deleteorder | deleteorder i/ID | ++---------------------+----------------------------------------------------+ +| updateorder | updateorder i/ID [n/NAME q/QUANTITY d/DATE] | ++---------------------+----------------------------------------------------+ +| listorder | listorder {i/ID n/NAME q/QUANTITY d/DATE | +| | s/STATUS sort/COLUMN_NAME rsort/COLUMN_NAME} | ++---------------------+----------------------------------------------------+ +| archiveorder | archiveorder d/DATE | ++---------------------+----------------------------------------------------+ +| receiveorder | receiveorder i/ID p/PRICE e/EXPIRY_DATE | +| | (d/DESCRIPTION m/MAX_QUANTITY) | ++---------------------+----------------------------------------------------+ +| purge | purge | ++---------------------+----------------------------------------------------+ +| help | help | ++---------------------+----------------------------------------------------+ +| exit | exit | ++---------------------+----------------------------------------------------+ +For more information, refer to User Guide: https://ay2122s1-cs2113t-t10-1.github.io/tp/ + ``` + +### Exiting MediVault : `exit` + +Should you want to quit the program, you can use the `exit` command. + +Format: `exit` + +Example: `exit` + +Expected Output: + +``` +Quitting MediVault... +``` + +## Data Handling + +### Data Storage + +MediVault will automatically save your data after any operation that modifies stock, order or prescriptions. The data +will be stored in 3 separate files `data/stock.txt`, `data/order.txt` and `data/prescription.txt`. Data is saved in a +specific format with fields delimited by a pipe `|`. + +Data formats: + +* For `data/stock.txt`: + * `ID|NAME|PRICE|QUANTITY|EXPIRY_DATE|DESCRIPTION|MAX_QUANTITY|ISDELETED` +* For `data/order.txt`: + * `ID|NAME|QUANTITY|DATE|STATUS` +* For `data/prescription.txt`: + * `ID|NAME|QUANTITY|CUSTOMER_ID|DATE|STAFF|STOCK_ID` + +### Data Editing + +> :warning: Warning: +> * It is possible for you to directly edit the data files, but it is **NOT** recommended unless you know exactly what you are doing because you risk corrupting it. +> * If MediVault detects corruption or invalid data, you will **NOT** be able to start MediVault. +> * In order for MediVault to work, you have to fix the error in the data file. +> * Invalid data detected by MediVault will be highlighted on launch to hint you in the direction to fix it. +> * In the worst case scenario where you are unable to fix it, you may have to delete the corresponding data file. +> * It may result in **unintended behaviour** if data file is tampered with while the program is running. +> * Editing the data directly poses a significant risk to corruption of data and may lead to **unintended behaviour**. ## FAQ -**Q**: How do I transfer my data to another computer? +**Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: You can transfer data to another computer by moving the `data` folder containing the 3 data files to where `MediVault.jar` is. +You should expect to see `stock.txt`, `order.txt`, `prescription.txt` in that folder. -## Command Summary +**Q**: Can MediVault run on a Raspberry Pi? + +**A**: Yes, MediVault does not require a lot of processing power and only requires any computer capable of running **Java 11**. -{Give a 'cheat sheet' of commands here} +**Q**: How do I transfer my data from Excel to MediVault? + +**A**: +- For Stocks: + - First, order the columns of your Excel sheet in the following order. + - `ID|NAME|PRICE|QUANTITY|EXPIRY_DATE|DESCRIPTION|MAX_QUANTITY|ISDELETED` + - Next, copy and paste the following command into Excel at the first row and on the right of last column of your data **(Cell I2)**. + - `=UPPER(CONCAT(A2,"|",B2,"|",C2,"|",D2,"|",TEXT(E2, "dd-mm-yyyy"),"|",F2,"|",G2,"|",H2))` + - Then, double-click on the square box at the bottom right of the cell so that the formula will be calculated for all rows. + - Finally, copy and paste the all entries in column **I** into `data/stock.txt`. +- For Orders: + - First, order the columns of your Excel sheet in the following order. + - `ID|NAME|QUANTITY|DATE|STATUS` + - Next, copy and paste the following command into Excel at the first row and on the right of last column of your data **(Cell F2)**. + - `=UPPER(CONCAT(A2,"|",B2,"|",C2,"|",TEXT(D2, "dd-mm-yyyy"),"|",E2))` + - Then, double-click on the square box at the bottom right of the cell so that the formula will be calculated for all rows. + - Finally, copy and paste the all entries in column **F** into `data/order.txt`. +- For Prescriptions: + - First, order the columns of your Excel sheet in the following order. + - `ID|NAME|QUANTITY|CUSTOMER_ID|DATE|STAFF|STOCK_ID` + - Next, copy and paste the following command into Excel at the first row and on the right of last column of your data **(Cell H2)**. + - `=UPPER(CONCAT(A2,"|",B2,"|",C2,"|",D2,"|",TEXT(E2, "dd-mm-yyyy"),"|",F2,"|",G2))` + - Then, double-click on the square box at the bottom right of the cell so that the formula will be calculated for all rows. + - Finally, copy and paste the all entries in column **H** into `data/prescription.txt`. + +**Q**: How secure is MediVault since it stores customer's information? + +**A**: MediVault does not require internet access and runs locally on the computer. For better security, do not connect +the computer to the internet and ensure that the computer is password locked and only give authorised staff access. + + +## Command Summary -* Add todo `todo n/TODO_NAME d/DEADLINE` +Command | Command Syntax +------ | ------ +addstock | `addstock n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE (d/DESCRIPTION m/MAX_QUANTITY)` +deletestock | `deletestock [i/ID expiring/DATE]` +updatestock | `updatestock i/ID [n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE d/DESCRIPTION m/MAX_QUANTITY]` +liststock | `liststock {i/ID n/NAME p/PRICE q/QUANTITY low/LESS_THAN_OR_EQUAL_QUANTITY e/EXPIRY_DATE expiring/LESS_THAN_OR_EQUAL_EXPIRY_DATE d/DESCRIPTION m/MAX_QUANTITY sort/COLUMN_NAME rsort/COLUMN_NAME}` +addprescription | `addprescription n/NAME q/QUANTITY c/CUSTOMER_ID s/STAFF_NAME` +deleteprescription | `deleteprescription i/ID` +updateprescription | `updateprescription i/ID [n/NAME q/QUANTITY c/CUSTOMER_ID d/DATE s/STAFF_NAME]` +listprescription | `listprescription {i/ID n/NAME q/QUANTITY c/CUSTOMER_ID d/DATE s/STAFF_NAME sid/STOCK_ID sort/COLUMN_NAME rsort/COLUMN_NAME}` +archiveprescription | `archiveprescription d/DATE` +addorder | `addorder n/NAME q/QUANTITY {d/DATE}` +deleteorder | `deleteorder i/ID` +updateorder | `updateorder i/ID [n/NAME q/QUANTITY d/DATE]` +listorder | `listorder {i/ID n/NAME q/QUANTITY d/DATE s/STATUS sort/COLUMN_NAME rsort/COLUMN_NAME}` +archiveorder | `archiveorder d/DATE` +receiveorder | `receiveorder i/ID p/PRICE e/EXPIRY_DATE (d/DESCRIPTION m/MAX_QUANTITY)` +purge | `no parameters` +help | `no parameters` +exit | `no parameters` diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..059155b857 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,3 @@ +theme: jekyll-theme-cayman +gems: + - jemoji diff --git a/docs/diagrams/AddOrderSequenceDiagram.puml b/docs/diagrams/AddOrderSequenceDiagram.puml new file mode 100644 index 0000000000..f18d2e6602 --- /dev/null +++ b/docs/diagrams/AddOrderSequenceDiagram.puml @@ -0,0 +1,56 @@ +@startuml AddOrderSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":AddOrderCommand" as addorder COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:Ui" as ui COLOR_UTILITIES + participant ":OrderManager" as ordermanager COLOR_UTILITIES + participant ":StockManager" as stockmanager COLOR_UTILITIES +end box + +autoactivate on +mainFrame sd Logic for AddOrderCommand +activate addorder +alt orderQuantity != 0 + alt nameExistsInOrder && !nameExistsInStock + alt orderQuantity < maxQuantity + addorder -> addorder : addDate(dateToAdd) + addorder --> addorder + addorder -> addorder : addOrder(ui: Ui, medicines: ArrayList, name: String, \nquantity: int, date: Date) + addorder --> addorder + else !orderQuantity < maxQuantity + addorder -> ui : print() + addorder <-- ui + end + else nameExistsInOrder && nameExistsInStock + addorder -> ordermanager : getTotalOrderQuantity() + addorder <-- ordermanager : TotalOrderQuantity + addorder -> stockmanager : getTotalStockQuantity() + addorder <-- stockmanager : TotalStockQuantity + addorder -> stockmanager : getMaxStockQuantity() + addorder <-- stockmanager : MaxStockQuantity + alt orderQuantity + totalExistingQuantity <= maxQuantity + addorder -> addorder : addDate(dateToAdd) + addorder --> addorder + addorder -> addorder : addOrder(ui: Ui, medicines: ArrayList, name: String, \nquantity: int, date: Date) + addorder --> addorder + else !orderQuantity + totalExistingQuantity <= maxQuantity + addorder -> ui : print() + addorder <-- ui + end + note right + This diagram shows the process for + adding order when medicine exist in + orders. + It does not include the process by + which medicine does not exist in + orders. + end note +end +deactivate addorder +@enduml \ No newline at end of file diff --git a/docs/diagrams/AddPrescriptionSequenceDiagram.puml b/docs/diagrams/AddPrescriptionSequenceDiagram.puml new file mode 100644 index 0000000000..f4c81206e1 --- /dev/null +++ b/docs/diagrams/AddPrescriptionSequenceDiagram.puml @@ -0,0 +1,64 @@ +@startuml AddPrescriptionSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":AddPrescriptionCommand" as addprescription COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:PrescriptionManager" as prescriptionManager COLOR_UTILITIES + participant "<>\n:StockManager" as stockManager COLOR_UTILITIES + participant ":StockComparator" as stockcomparator COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":Stock" as stock COLOR_INVENTORY +end box + +autoactivate on +mainframe sd AddPrescriptionCommand +activate addprescription + addprescription -> stockManager: getFilteredStocksByName(medicines: ArrayList, stockName: String) + addprescription <-- stockManager: filteredStocks + create stockcomparator + addprescription -> stockcomparator : new StockComparator() + addprescription <-- stockcomparator : filteredStocks + + addprescription -> prescriptionManager: getNotExpiredStockQuantity(medicines: ArrayList, \nname: String, prescribeDate: Date) + addprescription <-- prescriptionManager: totalStock + + + + deactivate stockcomparator + addprescription -> addprescription : checkExpiredMedication(ui: Ui, filteredStocks: ArrayList, \nprescriptionQuantity: int) + + loop stock : filteredStocks + opt existingExpiry.after(prescribeDate) || prescribeDateString.equals(expiryString) + opt existingQuantity == quantityToPrescribe + addprescription -> addprescription : prescribe(ui: Ui, medicines: ArrayList, medicationName: String, \ncustomerId: String, staffName: String, quantityToPrescribe: int, prescribeDate: Date, \nstock: Stock, existingId: int, existingExpiry: Date, setStockValue: int) + addprescription -> stock : setQuantity() + addprescription <-- stock + addprescription --> addprescription + end + + opt existingQuantity > quantityToPrescribe + addprescription -> addprescription : prescribe(ui: Ui, medicines: ArrayList, medicationName: String, \ncustomerId: String, staffName: String, quantityToPrescribe: int, prescribeDate: Date, \nstock: Stock, existingId: int, existingExpiry: Date, setStockValue: int) + addprescription -> stock : setQuantity() + addprescription <-- stock + addprescription --> addprescription + end + opt existingQuantity < quantityToPrescribe + addprescription -> addprescription : prescribe(ui: Ui, medicines: ArrayList, medicationName: String, \ncustomerId: String, staffName: String, quantityToPrescribe: int, prescribeDate: Date, \nstock: Stock, existingId: int, existingExpiry: Date, setStockValue: int) + addprescription -> stock : setQuantity() + addprescription <-- stock + addprescription --> addprescription + end + end + end + addprescription --> addprescription + + + +@enduml diff --git a/docs/diagrams/AddStockSequenceDiagram.puml b/docs/diagrams/AddStockSequenceDiagram.puml new file mode 100644 index 0000000000..25bd08e9cb --- /dev/null +++ b/docs/diagrams/AddStockSequenceDiagram.puml @@ -0,0 +1,65 @@ +@startuml AddStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":AddStockCommand" as addstock COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant ":StockValidator" as stockvalidator COLOR_UTILITIES + participant "<>\n:StockManager" as stockmanager COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":Stock" as stock COLOR_INVENTORY +end box + +autoactivate on +mainFrame sd Logic for AddStockCommand +activate addstock +addstock -> addstock : checkExpiryDate(ui: Ui, expiryDate: String) +addstock --> addstock : + + addstock -> stockmanager: getTotalStockQuantity() + addstock <-- stockmanager: totalStock + addstock -> addstock : isExpiryExist(ui: Ui, stockValidator: StockValidator, \nfilteredStock: ArrayList, quantityToAdd: String, \nformatExpiry: Date, totalStock: int) + loop stock : filteredStocks + opt isValidQuantity + addstock -> addstock : isValidQuantity(ui:Ui, stockValidator: StockValidator, \nmaxQuantity: int, quantity: int) + addstock -> stockvalidator : quantityValidityChecker(ui: Ui, quantity: int, maxQuantity: int) + addstock <-- stockvalidator : isValidQuantity + addstock --> addstock : + opt formatExpiry.equals(stock.getExpiry()) + addstock -> stock : getQuantity() + addstock <-- stock : getQuantity + addstock -> stock : setQuantity() + addstock <-- stock : + end + end + end + addstock --> addstock : + addstock -> addstock : checkValidParametersAndValues(ui: Ui, parameters: LinkedHashMap, \nmedicines: ArrayList, requiredParameters: String[], optionalParameters: String[]) + addstock -> addstock : addSameMedicine(ui: Ui, medicines: ArrayList, nameToAdd: String, \nstockValidator: StockValidator, filteredStocks: ArrayList, quantityToAdd: String, \nformatExpiry: Date, totalStock: int) + loop stock : filteredStocks + opt isValidQuantity + addstock -> addstock : isValidQuantity(ui:Ui, stockValidator: StockValidator, \nmaxQuantity: int, quantity: int) + addstock -> stockvalidator : quantityValidityChecker(ui: Ui, quantity: int, maxQuantity: int) + addstock <-- stockvalidator : isValidQuantity + addstock --> addstock : + addstock -> addstock : addMedicine(ui: Ui, medicines: ArrayList, name: String, \ndescription: String, price: Double, quantity: int, expiryDate: Date, maxQuantity: int) + addstock --> addstock : + end + addstock --> addstock : + end + addstock --> addstock : + note right + This diagram shows the process for adding medication + where medication exists. + It does not include the process whereby medication does + not exist in stock. + end note +deactivate addstock + +@enduml diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml new file mode 100644 index 0000000000..b8bf6ffb2e --- /dev/null +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -0,0 +1,31 @@ +@startuml ArchitectureDiagram +!include +!include +!include style.puml + +' hide the circles in the diagram +hide circle +hide empty members + +package " "<>{ + class MediVault COLOR_MEDIVAULT + class Utilities COLOR_UTILITIES + class Errors COLOR_ERRORS + class Command COLOR_COMMAND + class Inventory COLOR_INVENTORY +} + +class "<$user>" as User COLOR_SPARE +class "<$documents>" as Files COLOR_SPARE1 + +User .[COLOR_SPARE].> Utilities +MediVault -[COLOR_MEDIVAULT]> Utilities +Errors --[COLOR_ERRORS]up> Utilities +Utilities .[COLOR_UTILITIES]right>Files +Utilities --[COLOR_UTILITIES]> Command +MediVault --[COLOR_COMMAND]> Command +Command --[COLOR_COMMAND]> Inventory +Inventory --[COLOR_INVENTORY]> Utilities + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/ArchiveOrderCommand.puml b/docs/diagrams/ArchiveOrderCommand.puml new file mode 100644 index 0000000000..d56bea94a1 --- /dev/null +++ b/docs/diagrams/ArchiveOrderCommand.puml @@ -0,0 +1,28 @@ +@startuml DeleteStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":ArchiveOrderCommand" as archiveOrder COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:DateParser" as dateParser COLOR_UTILITIES + participant "<>\n:Storage" as storage COLOR_UTILITIES +end box + +autoactivate on +mainFrame sd Logic for ArchiveOrderCommand +activate archiveOrder +archiveOrder -> dateParser : stringToDate(date: String) +archiveOrder <-- dateParser : date +archiveOrder -> archiveOrder : ordersToArchive(medicines: ArrayList,\n orderArchiveDate: Date) +archiveOrder --> archiveOrder : filteredOrders +archiveOrder -> archiveOrder : removeFromOrders(medicines: ArrayList,\n filteredOrders: ArrayList) +archiveOrder --> archiveOrder +archiveOrder -> storage : getInstance() +archiveOrder <-- storage +archiveOrder -> storage : archiveData(filteredOrders: ArrayList) +archiveOrder <-- storage +@enduml \ No newline at end of file diff --git a/docs/diagrams/ArchivePrescriptionCommand.puml b/docs/diagrams/ArchivePrescriptionCommand.puml new file mode 100644 index 0000000000..9b78a45aec --- /dev/null +++ b/docs/diagrams/ArchivePrescriptionCommand.puml @@ -0,0 +1,28 @@ +@startuml DeleteStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":ArchivePrescriptionCommand" as archivePrescription COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:DateParser" as dateParser COLOR_UTILITIES + participant "<>\n:Storage" as storage COLOR_UTILITIES +end box + +mainFrame sd Logic for ArchivePrescriptionCommand +autoactivate on +activate archivePrescription +archivePrescription -> dateParser : stringToDate(date: String) +archivePrescription <-- dateParser : date +archivePrescription -> archivePrescription : prescriptionsToArchive(medicines: ArrayList,\n prescriptionArchiveDate: Date) +archivePrescription --> archivePrescription : filteredPrescriptions +archivePrescription -> archivePrescription : removeFromPrescriptions(medicines: ArrayList,\n filteredPrescriptions: ArrayList) +archivePrescription --> archivePrescription +archivePrescription -> storage : getInstance() +archivePrescription <-- storage +archivePrescription -> storage : archiveData(filteredPrescriptions: ArrayList) +archivePrescription <-- storage +@enduml \ No newline at end of file diff --git a/docs/diagrams/CommandClassDiagram.puml b/docs/diagrams/CommandClassDiagram.puml new file mode 100644 index 0000000000..3da3935176 --- /dev/null +++ b/docs/diagrams/CommandClassDiagram.puml @@ -0,0 +1,205 @@ +@startuml +'https://plantuml.com/class-diagram + +!include style.puml +!define ABSTRACT {abstract} + +' hide the circles in the diagram +hide circle +' hide the icons for access modifiers +skinparam classAttributeIconSize 0 +' to join all the arrows +'skinparam groupInheritance 3 +skinparam classBackgroundColor COLOR_COMMAND_BOX + +package "Command Component" <> { + class "{abstract}\nCommand" as Command + class AddPrescriptionCommand + class AddStockCommand + class AddOrderCommand + class ArchivePrescriptionCommand + class ArchiveOrderCommand + class DeletePrescriptionCommand + class DeleteStockCommand + class DeleteOrderCommand + class ExitCommand + class HelpCommand + class ListPrescriptionCommand + class ListStockCommand + class ListOrderCommand + class PurgeCommand + class ReceiveOrderCommand + class UpdatePrescriptionCommand + class UpdateStockCommand + class UpdateOrderCommand + +} + +package MediVault <> COLOR_MEDIVAULT_BOX { +} + +package Inventory <> COLOR_INVENTORY_BOX { +} + +class Command { + #parameters: LinkedHashMap + +execute(): void ABSTRACT +} + +class AddPrescriptionCommand { + +AddPrescriptionCommand() + +execute(): void + -prescribe(): void +} + +class AddStockCommand { + +AddStockCommand() + +execute(): void + -checkExpiryDate(): Date + -isExpiryExist(): boolean + -addSameMedicine(): boolean + -isValidQuantity(): boolean + -checkValidParametersAndValues(): boolean + -addMedicine(): void +} + +class AddOrderCommand { + +AddOrderCommand() + +execute(): void + -addOrder(): void + -addDate(): Date + -checkvalidParameterValues(): boolean +} + +class ArchivePrescriptionCommand { + +ArchivePrescriptionCommand() + +execute(): void + -prescriptionsToArchive(): ArrayList + -removeFromPrescriptions(): void +} + +class ArchiveOrderCommand { + +ArchiveOrderCommand() + +execute(): void + -ordersToArchive(): ArrayList + -removeFromOrders(): void +} + +class DeletePrescriptionCommand { + +DeletePrescriptionCommand() + +execute(): void + -isValidPrescriptionParameters(): boolean + -setStockQuantity(): boolean +} + +class DeleteStockCommand { + +DeleteStockCommand() + +execute(): void + -deleteStockById(): void + -deleteStockByExpiry(): void +} + +class DeleteOrderCommand { + +DeleteOrderCommand() + +execute(): void + -isValidOrderParameters(): boolean +} + +class ExitCommand { + +execute(): void +} + +class HelpCommand { + +execute(): void +} + +class ListPrescriptionCommand { + +ListPrescriptionCommand() + +execute(): void + -filterPrescriptions(): ArrayList +} + +class ListStockCommand { + +ListStockCommand() + +execute(): void +} + +class ListOrderCommand { + +ListOrderCommand() + +execute(): void + -filterOrders(): ArrayList +} + +class PurgeCommand { + +execute(): void +} + +class ReceiveOrderCommand { + +ReceiveOrderCommand() + +execute(): void + -getCurrentQuantity(): int + -checkStockExist(): boolean + -checkOrderIdExist(): boolean + -isStockParametersValid(): boolean +} + +class UpdatePrescriptionCommand { + +UpdatePrescriptionCommand() + +execute(): void + -processGivenNameAndQuantity(): boolean + -processGivenName(): boolean + -processGivenQuantity(): boolean + -processOtherFields(): boolean + -processPrescription(): boolean + -processRestoration(): boolean +} + +class UpdateOrderCommand { + +UpdateOrderCommand() + +execute(): void + -setUpdatesByOrderId(): void +} + +class UpdateStockCommand { + +UpdateStockCommand() + +execute(): void + -printUpdatedStockId: void + -checkAffectedCommand(): boolean + -getNewStock(): Stock + -addNewRowForUpdates(): void + -processDateInput(): boolean + -processQuantityValues(): boolean + -setUpdatesByStockId(): void +} + +AddPrescriptionCommand ----|> Command +AddStockCommand ----|> Command +AddOrderCommand ----|> Command +DeletePrescriptionCommand ---|> Command +DeleteStockCommand ---|> Command +DeleteOrderCommand ---|> Command +ExitCommand --|> Command +HelpCommand --|> Command +PurgeCommand --|> Command +ListPrescriptionCommand --up|> Command +ListStockCommand --up|> Command +ListOrderCommand --up|> Command +ReceiveOrderCommand ---up|> Command +ArchivePrescriptionCommand ---up|> Command +ArchiveOrderCommand ---up|> Command +UpdateOrderCommand ----up|> Command +UpdatePrescriptionCommand ----up|> Command +UpdateStockCommand ----up|> Command + +MediVault ---.right|> Command: executes > +Command ---.right|> Inventory: affects > + +note as ConstraintsNote +The input parameters for +methods and constructors are +left out as member details +can get outdated over time. +end note + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/ContainValidParametersAndValuesSequenceDiagram.puml b/docs/diagrams/ContainValidParametersAndValuesSequenceDiagram.puml new file mode 100644 index 0000000000..471d8ad290 --- /dev/null +++ b/docs/diagrams/ContainValidParametersAndValuesSequenceDiagram.puml @@ -0,0 +1,55 @@ +@startuml ContainValidParametersAndValuesSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":*Command" as command COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:Ui" as ui COLOR_UTILITIES + participant "<>\nMedicineValidator" as medicinevalidator COLOR_UTILITIES + participant ":*Validator" as xvalidator COLOR_UTILITIES + participant "<>\n:Storage" as storage COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant "<>\n:Medicine" as medicine COLOR_INVENTORY +end box + +autoactivate on +mainFrame sd Execution of *Command +note right of command +Replace * in the diagram with +Stock, Prescription or Order +depending on the command entered. +end note +activate command + command -> ui: getInstance() + command <-- ui + command -> medicine: getInstance() + command <-- medicine + create xvalidator + command -> xvalidator: new *Validator() + command <-- xvalidator + command -> medicinevalidator: containsInvalidParametersAndValues(ui: Ui,\nparameters: LinkedHashMap,\nmedicines: ArrayList, requiredParameters: String[],\noptionalParameters: String[], commandSyntax: String,\nrequiresOptionalParameters: boolean, validator: MedicineValidator) + medicinevalidator -> medicinevalidator: containsInvalidParameters(ui: Ui,\nparameters: LinkedHashMap,\nrequiredParameters: String[],\noptionalParameters: String[],\ncommandSyntax: String,\nrequiresOptionalParameters: boolean) + medicinevalidator --> medicinevalidator: isInvalidParameter + opt !isInvalidParameter + medicinevalidator -> xvalidator: containsInvalidParameterValues(ui: Ui,\nparameters: LinkedHashMap,\nmedicines: ArrayList,\ncommandSyntax: String,\ninvalidParameters: ArrayList) + medicinevalidator <-- xvalidator: isInvalidParameterValues + end + command <-- medicinevalidator: isInvalidInput + opt !isInvalidInput + ref over command, storage + Logic for *Command + end ref + command -> storage: getInstance() + command <-- storage + command -> storage: saveData() + command <-- storage + end + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/DeleteOrderSequenceDiagram.puml b/docs/diagrams/DeleteOrderSequenceDiagram.puml new file mode 100644 index 0000000000..b2af1afc39 --- /dev/null +++ b/docs/diagrams/DeleteOrderSequenceDiagram.puml @@ -0,0 +1,44 @@ +@startuml DeleteOrderSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":DeleteOrderCommand" as deleteorder COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant ":OrderValidator" as ordervalidator COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant "<>\n:Medicine" as medicine COLOR_INVENTORY + participant ":Order" as order COLOR_INVENTORY +end box +autoactivate on + +mainframe sd DeleteOrderCommand +activate deleteorder + +deleteorder -> deleteorder : isValidOrderParameters(ui: Ui, medicines: ArrayList) + + + + loop medicine : medicines + opt medicine instanceof Order + + + deleteorder -> order : getOrderId() + deleteorder <-- order + opt order.getOrderId() == order.orderId + deleteorder -> medicine : remove() + deleteorder <-- medicine : + end + end + end + + +deleteorder --> deleteorder : + + +@enduml diff --git a/docs/diagrams/DeletePrescriptionSequenceDiagram.puml b/docs/diagrams/DeletePrescriptionSequenceDiagram.puml new file mode 100644 index 0000000000..e35c5d1087 --- /dev/null +++ b/docs/diagrams/DeletePrescriptionSequenceDiagram.puml @@ -0,0 +1,51 @@ +@startuml DeletePrescriptionSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":DeletePrescriptionCommand" as deleteprescription COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:PrescriptionManager" as prescriptionManager COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":Stock" as stock COLOR_INVENTORY +end box + +autoactivate on +mainframe sd DeletePrescriptionCommand +activate deleteprescription + +deleteprescription -> deleteprescription : isValidPrescriptionParameters(ui: Ui, medicines: ArrayList) + + deleteprescription -> deleteprescription : setStockQuantity() + loop medicine : medicines + + opt medicine intanceof Stock + + opt prescription.getPrescriptionId() == prescriptionId + deleteprescription -> deleteprescription : setStockQuantity(ui Ui, medicines: ArrayList, \nstockIdPrescribe: int, prescribeQuantity: int) + opt stock.isDeleted + deleteprescription -> stock : setDeleted() + deleteprescription <-- stock : setAsDeleted + + deleteprescription -> stock : getStockID() + deleteprescription <-- stock : + opt stock.getStockID() == stockIdToDispense + deleteprescription -> stock : setQuantity() + deleteprescription <-- stock : + end + deleteprescription -->deleteprescription + end + + end + end + end + +deactivate deleteprescription +deleteprescription --> deleteprescription : + +@enduml diff --git a/docs/diagrams/DeleteStockSequenceDiagram.puml b/docs/diagrams/DeleteStockSequenceDiagram.puml new file mode 100644 index 0000000000..524bcbb014 --- /dev/null +++ b/docs/diagrams/DeleteStockSequenceDiagram.puml @@ -0,0 +1,34 @@ +@startuml DeleteStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":DeleteStockCommand" as deleteStock COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:Ui" as ui COLOR_UTILITIES + participant ":StockValidator" as stockValidator COLOR_UTILITIES + participant "<>\n:Storage" as storage COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant "<>\n:Medicine" as medicine COLOR_INVENTORY +end box + +autoactivate on +mainFrame sd Logic for DeleteStockCommand +activate deleteStock +opt !(hasStockId && hasExpiryDate) + alt hasStockId && !hasExpiryDate + ref over deleteStock, medicine + Deletion of stock by id + end ref + else !hasStockId && hasExpiryDate + ref over deleteStock, medicine + Deletion of stock by expiry + end ref + end +end +@enduml \ No newline at end of file diff --git a/docs/diagrams/DeletionOfStockByExpirySequenceDiagram.puml b/docs/diagrams/DeletionOfStockByExpirySequenceDiagram.puml new file mode 100644 index 0000000000..6546bbf6be --- /dev/null +++ b/docs/diagrams/DeletionOfStockByExpirySequenceDiagram.puml @@ -0,0 +1,42 @@ +@startuml DeleteStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":DeleteStockCommand" as deleteStock COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:Ui" as ui COLOR_UTILITIES + participant "<>\n:DateParser" as dateParser COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":Stock" as stock COLOR_INVENTORY +end box + +autoactivate on +mainFrame sd Deletion of stock by expiry +activate deleteStock +deleteStock -> deleteStock: deleteStockByExpiry(ui: Ui,\n medicines: ArrayList) +deleteStock -> dateParser : stringToDate(date: String) +deleteStock <-- dateParser : date +loop medicine : medicines + deleteStock -> deleteStock : medicine + opt medicine instanceof Stock + deleteStock -> stock : getExpiry() + deleteStock <-- stock : stockExpiryDate + opt stockExpiryDate.before(date) || stockExpiryDate.equals(date) + deleteStock -> stock : setQuantity(quantity: int) + deleteStock <-- stock + deleteStock -> stock : setDeleted(deleted: boolean) + deleteStock <-- stock + end + end + deleteStock --> deleteStock +end +deleteStock --> deleteStock +deleteStock -> ui : print() +deleteStock <-- ui +@enduml \ No newline at end of file diff --git a/docs/diagrams/DeletionOfStockByIdSequenceDiagram.puml b/docs/diagrams/DeletionOfStockByIdSequenceDiagram.puml new file mode 100644 index 0000000000..09f97b5116 --- /dev/null +++ b/docs/diagrams/DeletionOfStockByIdSequenceDiagram.puml @@ -0,0 +1,40 @@ +@startuml DeleteStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":DeleteStockCommand" as deleteStock COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:Ui" as ui COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":Stock" as stock COLOR_INVENTORY +end box + +autoactivate on +mainFrame sd Deletion of stock by id +activate deleteStock +deleteStock -> deleteStock: deleteStockById(ui: Ui,\n medicines: ArrayList) +loop medicine : medicines + deleteStock -> deleteStock : medicine + opt medicine instanceof Stock + deleteStock -> stock : getStockId() + deleteStock <-- stock : stockId + opt stock.getStockId() == stockId + deleteStock -> stock : setQuantity(quantity: int) + deleteStock <-- stock + deleteStock -> stock : setDeleted(deleted: boolean) + deleteStock <-- stock + end + end + deleteStock --> deleteStock +end +deleteStock --> deleteStock +deleteStock -> ui : print() +deleteStock <-- ui + +@enduml \ No newline at end of file diff --git a/docs/diagrams/InventoryClassDiagram.puml b/docs/diagrams/InventoryClassDiagram.puml new file mode 100644 index 0000000000..208048128a --- /dev/null +++ b/docs/diagrams/InventoryClassDiagram.puml @@ -0,0 +1,63 @@ +@startuml +'https://plantuml.com/class-diagram +' hide the circles in the diagram +hide circle +' hide the icons for access modifiers +skinparam classAttributeIconSize 0 +' to join all the arrows +skinparam groupInheritance 3 + +class "{abstract}\nMedicine"{ + #medicineName: String + #quantity: int + -{static} medicines: ArrayList + +{static} getInstance() + +Medicine(medicineName: String, quantity: int) + +toFileFormat() {abstract} + +toArchiveFormat() {abstract} +} + +class "Stock"{ + -{static} stockCount: int + #stockID: int + #price: double + #expiry: Date + #description: String + #maxQuantity: int + #isDeleted: boolean + +Stock(name: String, price: Double, quantity: int, + expiry: Date, description: String, maxQuantity: int) + +toFileFormat(): String + +toArchiveFormat(): String +} + +class "Prescription"{ + -{static} prescriptionCount: int + #prescriptionId: int + #customerId: int + #date: Date + #staff: String + #stockId: int + +Prescription(medicineName: String, quantity: int, + customerId: String, date: Date, staff: String, stockId: int) + +toFileFormat(): String + +toArchiveFormat(): String +} + +class "Order"{ + -{static} orderCount: int + #orderId: int + #date: Date + #isDelivered: boolean + +Order(medicineName: String, + quantity: int, date: Date) + +toFileFormat(): String + +toArchiveFormat(): String +} + +"{abstract}\nMedicine" <|-- "Stock" +"{abstract}\nMedicine" <|-- "Prescription" +"{abstract}\nMedicine" <|-- "Order" + +note "Note: All the getters and setters are\nleft out in this diagram." as n1 +@enduml \ No newline at end of file diff --git a/docs/diagrams/ListSequenceDiagram.puml b/docs/diagrams/ListSequenceDiagram.puml new file mode 100644 index 0000000000..c418d47ea5 --- /dev/null +++ b/docs/diagrams/ListSequenceDiagram.puml @@ -0,0 +1,62 @@ +@startuml AddSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":List*Command" as list COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\nUi" as ui COLOR_UTILITIES + participant ":*Comparator" as comparator COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":*" as object COLOR_INVENTORY +end box + +autoactivate on + +mainFrame sd Logic for List* + +activate list +list -> list : filter*() + +loop until end of parameters + + alt parameters.equals(SORT) + create comparator + list -> comparator : new *Comparator(column: String, isReversed: boolean) + list <-- comparator : sorted* + deactivate comparator + else parameters.equals(REVERSED_SORT) + create comparator + list -> comparator : new *Comparator(column: String, isReversed: boolean) + list <-- comparator : reverseSorted* + deactivate comparator + else default + loop until end of all * objects + list -> object : getAttributeValue() + list <-- object + end + + end + +end + +list --> list : filtered* + +list -> ui : print*(medicines: Arraylist<*>) +list <-- ui +deactivate ui + +note left of object +Replace * in the diagram with +Stock, Prescription or Order +depending on the command entered. +end note + + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/MainLogicSequenceDiagram.puml b/docs/diagrams/MainLogicSequenceDiagram.puml new file mode 100644 index 0000000000..9b01b68aab --- /dev/null +++ b/docs/diagrams/MainLogicSequenceDiagram.puml @@ -0,0 +1,63 @@ +@startuml AddSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box MediVault COLOR_MEDIVAULT_BOX + participant ":MediVault" as medivault COLOR_MEDIVAULT +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\nUi" as uiclass COLOR_UTILITIES + participant "Ui" as ui COLOR_UTILITIES + participant "CommandParser" as commandparser COLOR_UTILITIES +end box + +box Command COLOR_COMMAND_BOX + participant ":Command" as command COLOR_COMMAND +end box + +autoactivate on + +-> medivault: run() + medivault -> uiclass : getInstance() + medivault <-- uiclass + medivault -> uiclass : printWelcomeMessage() + medivault <-- uiclass + create commandparser + medivault -> commandparser : CommandParser() + medivault <-- commandparser + + loop until exit is received + medivault -> ui : getInput() + medivault <-- ui : userInput + medivault -> commandparser : parseCommand(userInput: String) + medivault <-- commandparser : userCommand, parameters + + alt command equals modes + medivault -> commandparser : changeMode(ui: Ui, commandString: String, mode: Mode) + medivault <-- commandparser + else else + medivault -> commandparser : processCommand(commandString: String,\ncommandParameters: LinkedHashMap,\nmode Mode) + commandparser -> commandparser : parseParameters(parametersString: String) + commandparser --> commandparser : parameterValues + create command + commandparser -> command : Command(parameters: LinkedHashMap) + commandparser <-- command + medivault <-- commandparser + medivault -> command : execute() + + ref over medivault,command + Execution of *command + end ref + medivault <-- command + deactivate command + end + end + +note left of command +Note that processCommand throws InvalidCommandException +when an invalid command is entered. +end note + +@enduml \ No newline at end of file diff --git a/docs/diagrams/ReceiveOrderSequenceDiagram.puml b/docs/diagrams/ReceiveOrderSequenceDiagram.puml new file mode 100644 index 0000000000..c912d157a7 --- /dev/null +++ b/docs/diagrams/ReceiveOrderSequenceDiagram.puml @@ -0,0 +1,41 @@ +@startuml AddSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":ReceiveOrderCommand" as receiveordercommand COLOR_COMMAND + participant ":AddStockCommand" as addstockcommand COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant ":StockValidator" as stockvalidator COLOR_UTILITIES +end box + +autoactivate on + +mainFrame sd Logic for ReceiveOrderCommand + +activate receiveordercommand +receiveordercommand -> receiveordercommand : isStockParametersValid(ui: Ui, medicines: ArrayList, name: String) +receiveordercommand -> stockvalidator : new StockValidator() +receiveordercommand <-- stockvalidator + +receiveordercommand -> receiveordercommand : checkStockExist(medicines: ArrayList, name: String) +receiveordercommand --> receiveordercommand +receiveordercommand -> stockvalidator : containsInvalidParametersAndValues(ui: Ui, medicines: ArrayList,\nparameters: LinkedHashMap, requiredParameters: String[],\noptionalParameters: String[], commandSyntax: String,\nrequiresOptionalParameters: boolean, validator: MedicineValidator) +receiveordercommand <-- stockvalidator : isInvalidInput + +receiveordercommand --> receiveordercommand : isStockParametersValid +opt isStockParametersValid + create addstockcommand + receiveordercommand -> addstockcommand : new AddStockCommand(parameters: LinkedHashMap) + receiveordercommand <-- addstockcommand + receiveordercommand -> addstockcommand : execute() + receiveordercommand <-- addstockcommand + deactivate addstockcommand +end + + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml new file mode 100644 index 0000000000..18126e3e57 --- /dev/null +++ b/docs/diagrams/StorageClassDiagram.puml @@ -0,0 +1,63 @@ +@startuml +'https://plantuml.com/class-diagram +' hide the circles in the diagram +hide circle +' hide the icons for access modifiers +skinparam classAttributeIconSize 0 +!include style.puml +skinparam classBackgroundColor COLOR_UTILITIES_BOX + +Storage -> FileParser + +class Storage { + -stockFile : File + -prescriptionFile : File + -orderFile : File + -prescriptionArchiveFile : File + -orderArchiveFile : File + + +saveData(medicines: ArrayList) : void + +archiveData(medicines: ArrayList) : void + +loadData() : ArrayList + -writeToFile(data: String, filePath: String) : void + -appendToFile(data: String, filePath: String) : void + -readFromFile(fileType: String, file: File) : ArrayList + -parseStockData(stockDetails: String, stockRow: int) : Medicine + -parseOrderData(orderDetails: String, orderRow: int) : Medicine + -parsePrescriptionData(prescriptionDetails: String, prescriptionRow: int) : Medicine +} + +class "FileParser"{ + -stockIds : HashSet + -orderIds : HashSet + -prescriptionIds : Hashset + + +parseStockId() : int + +parseStockName() : String + +parseStockPrice() : Double + +parseStockQuantity() : int + +parseStockExpiry() : Date + +parseStockDescription() : String + +parseStockMaxQuantity() : int + +parseStockIsDeleted() : boolean + +parseOrderId() : int + +parseOrderName() : String + +parseOrderQuantity() : int + +parseOrderDate() : Date + +parseOrderStatus() : boolean + +parsePrescriptionId() : int + +parsePrescriptionName() : String + +parsePrescriptionQuantity() : int + +parsePrescriptionCustomerId() : String + +parsePrescriptionDate() : Date + +parsePrescriptionStaff() : String + +parsePrescriptionStockId() : int +} + +note bottom +The input parameters are as follows: +parseStock*(splitStockDetails: String[], stockRow: int) +parseOrder*(splitOrderDetails: String[], orderRow: int) +parsePrescription*(splitPrescriptionDetails: String[], prescriptionRow: int) +end note +@enduml \ No newline at end of file diff --git a/docs/diagrams/UpdateOrderSequenceDiagram.puml b/docs/diagrams/UpdateOrderSequenceDiagram.puml new file mode 100644 index 0000000000..031582573d --- /dev/null +++ b/docs/diagrams/UpdateOrderSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml UpdateOrderSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":UpdateOrderCommand" as updateorder COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant ":StockValidator" as stockvalidator COLOR_UTILITIES + participant "<>\n:OrderManager" as ordermanager COLOR_UTILITIES + participant "<>\n:StockManager" as stockmanager COLOR_UTILITIES +end box + +box Inventory COLOR_INVENTORY_BOX + participant ":Order" as order COLOR_INVENTORY +end box + +autoactivate on +mainFrame sd Logic for UpdateOrderCommand +activate updateorder + updateorder -> ordermanager: extractOrderObject(parameters: LinkedHashMap,\nmedicines: ArrayList) + updateorder <-- ordermanager: order + updateorder -> order: isDelivered() + updateorder <-- order + opt !isDelivered + updateorder -> stockmanager: getMaxStockQuantity(medicines: ArrayList, orderName: String) + updateorder <-- stockmanager + opt existName && existQuantityParam + create stockvalidator + updateorder -> stockvalidator: new StockValidator() + updateorder <-- stockvalidator + updateorder -> stockvalidator: checkUpdateQuantity(ui: Ui,\nmedicines: ArrayList,\norder: Order, maxQuantity: int) + updateorder <-- stockvalidator: isValidQuantity + opt isValidQuantity + updateorder -> updateorder: setUpdatesByOrderId(filteredOrders: ArrayList,\norder: Order) + updateorder --> updateorder + end + end + end + +@enduml \ No newline at end of file diff --git a/docs/diagrams/UpdatePrescriptionSequenceDiagram.puml b/docs/diagrams/UpdatePrescriptionSequenceDiagram.puml new file mode 100644 index 0000000000..837d0a4601 --- /dev/null +++ b/docs/diagrams/UpdatePrescriptionSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml UpdatePrescriptionSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":UpdatePrescriptionCommand" as updateprescription COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant ":StockValidator" as stockvalidator COLOR_UTILITIES + participant "<>\n:PrescriptionManager" as prescriptionmanager COLOR_UTILITIES +end box + +autoactivate on +mainFrame sd Logic for UpdatePrescriptionCommand +activate updateprescription + updateprescription -> prescriptionmanager: extractDispenseObject(parameters: LinkedHashMap,\nmedicines: ArrayList) + updateprescription <-- prescriptionmanager + create stockvalidator + updateprescription -> stockvalidator: new StockValidator() + updateprescription <-- stockvalidator + alt hasNameParam && hasQuantityParam + updateprescription -> updateprescription: processGivenNameAndQuantity() + note right + All methods takes in the same parameters. + process(ui: Ui, medicines: ArrayList, + prescription: Prescription, customerId: String, + date: Date, staffName: String) + end note + updateprescription --> updateprescription: isSuccessfulUpdate + else hasNameParam && !hasQuantityParam + updateprescription -> updateprescription: processGivenName() + updateprescription --> updateprescription: isSuccessfulUpdate + else !hasNameParam && hasQuantityParam + updateprescription -> updateprescription: processGivenQuantity() + updateprescription --> updateprescription: isSuccessfulUpdate + else containsParamOtherThanNameAndQuantity + updateprescription -> updateprescription: processOtherFields() + updateprescription --> updateprescription: isSuccessfulUpdate + end + +@enduml \ No newline at end of file diff --git a/docs/diagrams/UpdateStockSequenceDiagram.puml b/docs/diagrams/UpdateStockSequenceDiagram.puml new file mode 100644 index 0000000000..51c7b65548 --- /dev/null +++ b/docs/diagrams/UpdateStockSequenceDiagram.puml @@ -0,0 +1,41 @@ +@startuml UpdateStockSequenceDiagram +'https://plantuml.com/sequence-diagram + +!include style.puml + +box Command COLOR_COMMAND_BOX + participant ":UpdateStockCommand" as updatestock COLOR_COMMAND +end box + +box Utilities COLOR_UTILITIES_BOX + participant "<>\n:StockManager" as stockmanager COLOR_UTILITIES +end box + +autoactivate on +mainFrame sd Logic for UpdateStockCommand +activate updatestock + updatestock -> stockmanager: extractStockObject(parameters: LinkedHashMap,\nmedicines: ArrayList) + updatestock <-- stockmanager: stock + + updatestock -> updatestock: processQuantityValues(ui: Ui, \nmedicines: ArrayList, stock: Stock) + updatestock --> updatestock: isValidQuantityValues + + updatestock -> updatestock: processDateInput(ui: Ui, \nmedicines: ArrayList, stock: Stock) + updatestock --> updatestock: isValidExpDate + + opt isValidQuantityValues && isValidExpDate + updatestock -> stockmanager: getFilteredStocksByName(medicines: ArrayList,\nstockName: String) + updatestock <-- stockmanager: oldFilteredStocks + opt parameters contains name + updatestock -> updatestock: addNewRowForUpdate(filteredStocks: ArrayList,\nmedicines: ArrayList) + deactivate updatestock + updatestock -> updatestock: getNewStock(medicines: ArrayList,\nstock: Stock) + deactivate updatestock + end + updatestock -> stockmanager: getFilteredStocksByName(medicines: ArrayList,\nstockName: String) + updatestock <-- stockmanager: filteredStocks + updatestock -> updatestock: setUpdatesByStockId(filteredStocks: ArrayList,\nstock: Stock) + deactivate updatestock + end + +@enduml \ No newline at end of file diff --git a/docs/diagrams/ValidatorClassDiagram.puml b/docs/diagrams/ValidatorClassDiagram.puml new file mode 100644 index 0000000000..970b9f4338 --- /dev/null +++ b/docs/diagrams/ValidatorClassDiagram.puml @@ -0,0 +1,63 @@ +@startuml +'https://plantuml.com/class-diagram +' hide the circles in the diagram +hide circle +' hide the icons for access modifiers +skinparam classAttributeIconSize 0 +' to join all the arrows +skinparam groupInheritance 3 + +!include style.puml + +skinparam classBackgroundColor COLOR_UTILITIES_BOX + +class "{abstract}\n MedicineValidator"{ + +containsInvalidParameterValues(): boolean {abstract} + +isValidColumn(): boolean {abstract} + +containsInvalidParametersAndValues(): boolean + +getInvalidParameters(): ArrayList + +containsInvalidParameters(): boolean + +isValidName(): boolean + +isValidQuantity(): boolean + +hasNameParamChecker(): boolean + +hasQuantityParamChecker(): boolean +} + +class "StockValidator"{ + +StockValidator() + +containsInvalidParameterValues(): boolean; + +isValidStockId(): boolean + +isValidPrice(): boolean + +isValidExpiry(): boolean + +isValidDescription(): boolean + +isValidMaxQuantity(): boolean + +isValidColumn(): boolean + +quantityValidityChecker(): boolean + +dateValidityChecker(): boolean +} + +class "PrescriptionValidator"{ + +PrescriptionValidator() + +containsInvalidParameterValues(): boolean; + +isValidPrescriptionId(): boolean + +isValidCustomerId(): boolean + +isValidStaffName(): boolean + +isValidColumn(): boolean + +isValidDate(): boolean +} + +class "OrderValidator"{ + +OrderValidator() + +containsInvalidParameterValues(): boolean; + +isValidOrderId(): boolean + +isValidDate(): boolean + +isValidStatus(): boolean + +isValidColumn(): boolean +} + +"{abstract}\n MedicineValidator" <|---- "StockValidator" +"{abstract}\n MedicineValidator" <|-- "PrescriptionValidator" +"{abstract}\n MedicineValidator" <|---- "OrderValidator" + +note "The input parameters for all functions\nhas been left out to make this\ndiagram more compact." as n1 +@enduml \ No newline at end of file diff --git a/docs/diagrams/diagram_images/AddOrderSequenceDiagram.png b/docs/diagrams/diagram_images/AddOrderSequenceDiagram.png new file mode 100644 index 0000000000..f9ff03828e Binary files /dev/null and b/docs/diagrams/diagram_images/AddOrderSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/AddPrescriptionSequenceDiagram.png b/docs/diagrams/diagram_images/AddPrescriptionSequenceDiagram.png new file mode 100644 index 0000000000..cbe2e354e8 Binary files /dev/null and b/docs/diagrams/diagram_images/AddPrescriptionSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/AddStockSequenceDiagram.png b/docs/diagrams/diagram_images/AddStockSequenceDiagram.png new file mode 100644 index 0000000000..330707c215 Binary files /dev/null and b/docs/diagrams/diagram_images/AddStockSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/ArchitectureDiagram.png b/docs/diagrams/diagram_images/ArchitectureDiagram.png new file mode 100644 index 0000000000..74bcab45cd Binary files /dev/null and b/docs/diagrams/diagram_images/ArchitectureDiagram.png differ diff --git a/docs/diagrams/diagram_images/ArchiveOrderSequenceDiagram.png b/docs/diagrams/diagram_images/ArchiveOrderSequenceDiagram.png new file mode 100644 index 0000000000..55ea13cd73 Binary files /dev/null and b/docs/diagrams/diagram_images/ArchiveOrderSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/ArchivePrescriptionSequenceDiagram.png b/docs/diagrams/diagram_images/ArchivePrescriptionSequenceDiagram.png new file mode 100644 index 0000000000..9e56a23802 Binary files /dev/null and b/docs/diagrams/diagram_images/ArchivePrescriptionSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/CommandClassDiagram.png b/docs/diagrams/diagram_images/CommandClassDiagram.png new file mode 100644 index 0000000000..87dfe04bb7 Binary files /dev/null and b/docs/diagrams/diagram_images/CommandClassDiagram.png differ diff --git a/docs/diagrams/diagram_images/ContainValidParametersAndValuesSequenceDiagram.png b/docs/diagrams/diagram_images/ContainValidParametersAndValuesSequenceDiagram.png new file mode 100644 index 0000000000..a53ef143f1 Binary files /dev/null and b/docs/diagrams/diagram_images/ContainValidParametersAndValuesSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/DeleteOrderSequenceDiagram.png b/docs/diagrams/diagram_images/DeleteOrderSequenceDiagram.png new file mode 100644 index 0000000000..4ac475c477 Binary files /dev/null and b/docs/diagrams/diagram_images/DeleteOrderSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/DeletePrescriptionSequenceDiagram.png b/docs/diagrams/diagram_images/DeletePrescriptionSequenceDiagram.png new file mode 100644 index 0000000000..6262c16b54 Binary files /dev/null and b/docs/diagrams/diagram_images/DeletePrescriptionSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/DeleteStockSequenceDiagram.png b/docs/diagrams/diagram_images/DeleteStockSequenceDiagram.png new file mode 100644 index 0000000000..6cfd864e8f Binary files /dev/null and b/docs/diagrams/diagram_images/DeleteStockSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/DeletionOfStockByExpirySequenceDiagram.png b/docs/diagrams/diagram_images/DeletionOfStockByExpirySequenceDiagram.png new file mode 100644 index 0000000000..36134ad9b4 Binary files /dev/null and b/docs/diagrams/diagram_images/DeletionOfStockByExpirySequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/DeletionOfStockByIdSequenceDiagram.png b/docs/diagrams/diagram_images/DeletionOfStockByIdSequenceDiagram.png new file mode 100644 index 0000000000..1e775d6e24 Binary files /dev/null and b/docs/diagrams/diagram_images/DeletionOfStockByIdSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/InventoryClassDiagram.png b/docs/diagrams/diagram_images/InventoryClassDiagram.png new file mode 100644 index 0000000000..a685670296 Binary files /dev/null and b/docs/diagrams/diagram_images/InventoryClassDiagram.png differ diff --git a/docs/diagrams/diagram_images/ListSequenceDiagram.png b/docs/diagrams/diagram_images/ListSequenceDiagram.png new file mode 100644 index 0000000000..e67b4f9178 Binary files /dev/null and b/docs/diagrams/diagram_images/ListSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/MainLogicSequenceDiagram.png b/docs/diagrams/diagram_images/MainLogicSequenceDiagram.png new file mode 100644 index 0000000000..ae35f8c84e Binary files /dev/null and b/docs/diagrams/diagram_images/MainLogicSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/ReceiveOrderSequenceDiagram.png b/docs/diagrams/diagram_images/ReceiveOrderSequenceDiagram.png new file mode 100644 index 0000000000..4886200a80 Binary files /dev/null and b/docs/diagrams/diagram_images/ReceiveOrderSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/StorageClassDiagram.png b/docs/diagrams/diagram_images/StorageClassDiagram.png new file mode 100644 index 0000000000..245add461f Binary files /dev/null and b/docs/diagrams/diagram_images/StorageClassDiagram.png differ diff --git a/docs/diagrams/diagram_images/UpdateOrderSequenceDiagram.png b/docs/diagrams/diagram_images/UpdateOrderSequenceDiagram.png new file mode 100644 index 0000000000..0caf406327 Binary files /dev/null and b/docs/diagrams/diagram_images/UpdateOrderSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/UpdatePrescriptionSequenceDiagram.png b/docs/diagrams/diagram_images/UpdatePrescriptionSequenceDiagram.png new file mode 100644 index 0000000000..46b606fe9a Binary files /dev/null and b/docs/diagrams/diagram_images/UpdatePrescriptionSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/UpdateStockSequenceDiagram.png b/docs/diagrams/diagram_images/UpdateStockSequenceDiagram.png new file mode 100644 index 0000000000..bcef386b68 Binary files /dev/null and b/docs/diagrams/diagram_images/UpdateStockSequenceDiagram.png differ diff --git a/docs/diagrams/diagram_images/ValidatorClassDiagram.png b/docs/diagrams/diagram_images/ValidatorClassDiagram.png new file mode 100644 index 0000000000..11ec7a71a4 Binary files /dev/null and b/docs/diagrams/diagram_images/ValidatorClassDiagram.png differ diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml new file mode 100644 index 0000000000..95a13a9e21 --- /dev/null +++ b/docs/diagrams/style.puml @@ -0,0 +1,17 @@ +!define COLOR_MEDIVAULT #1E90FF +!define COLOR_UTILITIES #FF8C00 +!define COLOR_COMMAND #00FFFF +!define COLOR_ERRORS #DC143C +!define COLOR_INVENTORY #BA55D3 +!define COLOR_SPARE #FF69B4 +!define COLOR_SPARE1 #FFFF00 +!define COLOR_SPARE3 #00FF00 + +!define COLOR_MEDIVAULT_BOX #ADD8E6 +!define COLOR_UTILITIES_BOX #FAEBD7 +!define COLOR_COMMAND_BOX #E0FFFF +!define COLOR_ERRORS_BOX #F08080 +!define COLOR_INVENTORY_BOX #DDA0DD +!define COLOR_SPARE_BOX #FFC0CB +!define COLOR_SPARE1_BOX #FFFACD +!define COLOR_SPARE3_BOX #98FB98 diff --git a/docs/team/a-tph.md b/docs/team/a-tph.md new file mode 100644 index 0000000000..cebf44eed8 --- /dev/null +++ b/docs/team/a-tph.md @@ -0,0 +1,69 @@ +# Teo Phing Huei, Aeron - Project Portfolio Page + +This is a student project for a university software development course and I am one of the contributors to `MediVault`. + +`MediVault` is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. + +## Summary of Contributions + +Given below are my contributions to the project. + +Code contributed: more than 3000 lines of +code. [[RepoSense](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=a-tph&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=a-tph&tabRepo=AY2122S1-CS2113T-T10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false)] + +### Features + +v1.0: + +* Implemented `updatestock` command. [[#25](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/25)] + +v2.0: + +* Implemented `updateorder` command. [[#121](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/121)] +* Implemented `updateprescription` command. [[#184](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/184)] + +v2.1: + +* Fixed functionality and documentation bugs raised during PE. [[#296](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/296)] +* Fixed bugs found in `updateprescription` command. [[#324](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/324)] +* Fixed bug found in `MedicineValidator` class. [[#328](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/328)] + +### Enhancements to Existing Features + +* Implemented universal `containsInvalidParameterValues()` method. [[#40](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/40)] + +* Included JUnit tests for: + * `StockValidator` [[#59](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/59)] + * `MedicineManager` [[#93](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/93)] + * `UpdateStockCommand`, `UpdateOrderCommand` and `UpdatePrescriptionCommand` [[#316](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/316)] + +* Wrote and refractored `Manager` classes for `stock`, `prescription` and `order`. [[#69](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/69), [#141](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/141)] + +* Added functionality for `deletestock` command to delete all expired stocks. [[#135](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/135)] + +### Documentation + +* [User Guide](../UserGuide.md) + * Added documentation for `updatestock`, `updateprescription` and `updateorder` commands. [[#186](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/186)] + +* [Developer Guide](../DeveloperGuide.md) + * Created skeleton Developer guide for the team. [[#210](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/210)] + * Included [[#213](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/213), [#270](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/270)] + * Design + * Architecture and Command components with class diagram and detailed explanation. + * Implementation + * Initial validation checker sequence diagram and detailed explanation under Main Logic + * `UpdateStockCommand`, `UpdatePrescriptionCommand` and `UpdateOrderCommand` sequence diagrams and detailed + explanation. + +### Team-Based + +* Created and contributed to [Trello](https://trello.com/b/nMVm0vgz/cs2113t-user-stories) dashboard. +* Attended weekly team meetings. +* Contributed to release managements. +* Ensured UG and DG has a consistent format. +* Pull Requests reviewed with non-trivial review comments. [[#171](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/171), [#295](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/295)] + +### Community + +* Reported bugs and provided suggestions for other teams in the class. [[#1](https://github.com/a-tph/ped/issues/1), [#2](https://github.com/a-tph/ped/issues/2), [#3](https://github.com/a-tph/ped/issues/3), [#4](https://github.com/a-tph/ped/issues/4), [#5](https://github.com/a-tph/ped/issues/5)] \ No newline at end of file diff --git a/docs/team/alvintan01.md b/docs/team/alvintan01.md new file mode 100644 index 0000000000..4bbffc37b5 --- /dev/null +++ b/docs/team/alvintan01.md @@ -0,0 +1,68 @@ +# Alvin Tan Guo Hao - Project Portfolio Page + +This is a student project for a university software development course and I am one of the contributors to `MediVault`. + +`MediVault` is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. +It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. + +## Summary of Contributions + +Given below are my contributions to the project. + +Code contributed: more than 4000 lines of +code. [[RepoSense](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=alvintan01&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=alvintan01&tabRepo=AY2122S1-CS2113T-T10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)] + +### Features + +v1.0 tasks: + +- Implemented skeleton code for MediVault. [[#10](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/10)] +- Implemented `Ui` class for MediVault. [[#10](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/10)] +- Implemented sort for `liststock` command. [[#45](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/45)] +- Implemented `exit`, `purge` and `help` command. [[#10](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/10), [#13](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/13), [#45](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/45)] + +v2.0 tasks: + +- Implemented `listprescription` command. [[#181](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/181)] +- Implemented `receiveorder` command. [[#208](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/208)] +- Implemented sort for `listorder` command and mode feature. [[#162](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/162), [#123](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/123)] + +v2.1 tasks: + +- Fixed bug where ID does not reset after `purge`, `receiveorder` does not mark an order as delivered and whitespace renders command invalid. [[#280](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/280)] +- Demo video for stock features. + +### Enhancements to Existing Features + +- Added order quantity to `liststock` command. [[#109](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/109)] +- Refactor `HashMap` to `LinkedHashMap`. [[#123](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/123)] +- Refactor `command` objects. [[#133](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/133)] +- Refactor `Dispense` to `Prescription` [[#208](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/208)] +- Included JUnit tests for `CommandParser`, `ListPrescription`, `Help`, `Purge` and sort for `ListOrder` and `ListStock`. [[#92](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/92), [#305](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/305)] + +### Documentation + +- [User Guide](../UserGuide.md) + - Added documentation for `mode`, `purge`, `exit`, `help`, `listprescription` and `receiveorder`. [[#97](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/97), [#208](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/208)] + - Added command summary. [[#97](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/97)] + - Reordered the sections into add, list, delete and update. [[#229](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/229)] + - Added glossary section [[#309](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/309)] + +- [Developer Guide](../DeveloperGuide.md) + - Design + - Main application logic and explanation. [[#163](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/163)] + - Validator class diagram and explanation. [[#198](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/198)] + - Inventory class diagram and explanation. [[#198](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/198)] + - Sections on `list` and `receiveorder` and their respective sequence diagrams. [[#163](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/163), [#223](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/223)] + +### Team-Based +- Setting up the GitHub team org/repo. +- Assigning user stories to team members. +- Maintaining [Trello](https://trello.com/b/nMVm0vgz/cs2113t-user-stories) dashboard. +- Hosting weekly team meetings. +- Release management. +- Ensured UG and DG has a consistent format. +- Pull Requests reviewed with non-trivial review comments. [[#74](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/74), [#143](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/143), [#172](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/172)] + +### Community +- Reported bugs and suggestions for other teams in the class. [[#1](https://github.com/alvintan01/ped/issues/1), [#2](https://github.com/alvintan01/ped/issues/2), [#3](https://github.com/alvintan01/ped/issues/3), [#4](https://github.com/alvintan01/ped/issues/4), [#5](https://github.com/alvintan01/ped/issues/5), [#6](https://github.com/alvintan01/ped/issues/6), [#7](https://github.com/alvintan01/ped/issues/7), [#8](https://github.com/alvintan01/ped/issues/8), [#9](https://github.com/alvintan01/ped/issues/9), [#10](https://github.com/alvintan01/ped/issues/10)] diff --git a/docs/team/deonchung.md b/docs/team/deonchung.md new file mode 100644 index 0000000000..7d65e5d75e --- /dev/null +++ b/docs/team/deonchung.md @@ -0,0 +1,69 @@ +# Deon Chung Hui - Project Portfolio Page + +This is a student project for a university software development course and I am one of the contributors to `MediVault`. + +`MediVault` is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. +It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. + +## Summary of Contributions + +Given below are my contributions to the project. + +Code contributed: more than 2300 lines of +code. [RepoSense](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=deonchung&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25) +### Features + +v1.0: + +* Implemented `addstock` command. [[#23](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/23)] + +v2.0: + +* Implemented `addprescription` command. [[#74](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/74)] +* Implemented `deleteprescription` command. [[#147](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/147)] +* Implemented `deleteorder` command. [[#119](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/119)] + +v2.1: + +* Fix bug in `addstock` where maximum quantity can be exceeded if the same medication with the same expiry date is added. [[#255](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/255)] +* Fix bug in `addprescription` where expired medication can be prescribed. [[#295](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/295)] +* Fix bug in `addprescription` where the wrong error message is shown. [[#306](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/306)] +* Demo video for prescription features. + +### Enhancements to Existing Features + +* Implemented `Prescription Validator` method. [[#86](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/86)] + +* Implemented `Order Validator` method. [[#119](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/119)] + +* Implemented `getNotExpiredStockQuantity` method in `PrescriptionManager`. [[#295](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/295)] + +* Added functionality for `addstock` command to limit number of medication for stock. [[#42](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/42)] + +* Included JUnit tests for: + * `Prescription Validator` and `Order Validator` class. [[#89](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/89) , [#140](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/140)] + * `AddStock`, `AddPrescription`, `DeletePrescription` and `DeleteStock`. [[#306](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/306)] + +### Documentation + +* [User Guide](../UserGuide.md) + * Added documentation for `addstock`, `addprescription`, `deleteprescription` and `deleteorder` commands. [[#96](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/96) , [#171](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/171)] + +* [Developer Guide](../DeveloperGuide.md) + * Acknowledgement. [[#194](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/194)] + * Setting up environment. [[#194](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/194)] + * Section on `addstock`, `addprescription` ,`deleteprescription` and `UpdateOrderCommand` and their respective sequence diagrams. [[#171](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/171)] + * User stories. [[#255](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/255)] + * Instruction for manual testing. [[#258](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/258)] + +### Team-Based + +* Attended all weekly team meetings. +* Documentation of acknowledgement, setting up environment, user stories and instruction for manual testing in DG. +* Pull Requests reviewed with non-trivial review comments [[#298](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/298)]. + +### Community + +* Reported bugs and suggestions for other teams in the class. [[#1](https://github.com/deonchung/ped/issues/1), [#2](https://github.com/deonchung/ped/issues/2), + [#3](https://github.com/deonchung/ped/issues/3), [#4](https://github.com/deonchung/ped/issues/4), [#5](https://github.com/deonchung/ped/issues/5), + [#6](https://github.com/deonchung/ped/issues/6), [#7](https://github.com/deonchung/ped/issues/7)] diff --git a/docs/team/jiangweichen835.md b/docs/team/jiangweichen835.md new file mode 100644 index 0000000000..0e78dc2fd3 --- /dev/null +++ b/docs/team/jiangweichen835.md @@ -0,0 +1,71 @@ +# Jiang Weichen - Project Portfolio Page + +This is a student project for a university software development course and I am one of the contributors to `MediVault`. + +`MediVault` is a Command Line Interface (CLI) application that will help to manage medication +supplies within a pharmacy. It is an integrated solution that provides real-time tracking of +stocks, prescriptions and orders. + +## Summary of Contributions + +Given below are my contributions to the project. + +Code contributed: more than 1000 lines of +code. [[RepoSense](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=jiangweichen835&tabRepo=AY2122S1-CS2113T-T10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false)] +### Features + +#### v1.0: + +* Implemented listing by medicine name and description to `liststock` command. + * Functionality: Users are able to search for medicine with one command. + * Justification: Users will be able to search for medicine by name or description. + * Pull request: [[#26](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/26)] + +#### v2.0: + +* Implemented `addorder` command. + * Functionality: Users are able to add order for medicine with one command. + * Justification: Users will be able to add medication's name, quantity and order date using a single command. + * Pull request: [[#139](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/139)] + +#### v2.1: + +* Fixed bug in `addorder` for medicine exist in stock but not in order + * Functionality: `addorder` should check for order quantity of first order of medicine that exists in stock. + * Justification: To prevent users from exceeding existing stock maximum quantity allowed. + * Pull request: [[#308](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/308)] +* Demo video for order features + +### Enhancements to Existing Features + +* Included JUnit tests for: + * `StockValidator` and `MedicineValidator` class + * Pull request: [[#91](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/91)] + * `ListStockCommand` class and `AddOrderCommand` class + * Pull request: [[#308](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/308)] + +* Added functionality to `addorder` command to set order date as optional and limit order quantity + * Functionality: User will not be able to add order if total existing quantity in stock and order quantity exceeds maximum quantity. + Order date will be set as the date when user placed the order if order parameter not given. + * Pull request: [[#172](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/172)] + +### Documentation + +* [User Guide](../UserGuide.md) + * Added documentations for `liststock` and `addorder` commands + * Pull request: [[#99](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/99) + , [#188](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/188)] + +* [Developer Guide](../DeveloperGuide.md) + * Implementation + * `addorder` sequence diagram and detailed explanation. + * Pull request: [[#174](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/174)] + +### Team-Based + +* Attended weekly team meetings. + +### Community + +* Reported bugs and suggestions for other teams in the class. [[#1](https://github.com/jiangweichen835/ped/issues/1), [#2](https://github.com/jiangweichen835/ped/issues/2), +[#3](https://github.com/jiangweichen835/ped/issues/3), [#4](https://github.com/jiangweichen835/ped/issues/4), [#5](https://github.com/jiangweichen835/ped/issues/5)] \ No newline at end of file diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/remusteo.md b/docs/team/remusteo.md new file mode 100644 index 0000000000..369e027d31 --- /dev/null +++ b/docs/team/remusteo.md @@ -0,0 +1,77 @@ +# Teo Chin Kai Remus - Project Portfolio Page + +This is a student project for a university software development course and I am one of the contributors to `MediVault`. + +`MediVault` is a Command Line Interface (CLI) application that will help to manage medication supplies within a pharmacy. It is an integrated solution that provides real-time tracking of stocks, prescriptions and orders. + +## Summary of Contributions + +Given below are my contributions to the project. + +Code contributed: more than 2400 lines of code. [[RepoSense](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=RemusTeo&tabRepo=AY2122S1-CS2113T-T10-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)] + +### Features + +#### v1.0: + +* Implemented `deletestock` command. [[#19](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/19)] +* Implemented `help` command. [[#27](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/27)] + +#### v2.0: + +* Implemented `listorder` command. [[#116](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/116), [#134](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/134), [#178](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/178)] +* Implemented `Storage` class. [[#127](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/127), [#183](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/183)] +* Implemented `FileParser` class. [[#154](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/154), [#200](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/200)] +* Implemented `archiveprescription` and `archiveorder` command. [[#193](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/193), [#226](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/226)] + +#### v2.1: + +* Fix bug for `archive*` commands where time affected exact date matching. [[#222](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/222/files)] +* Improved `archiveorder` and `archiveprescription` commands. [[#297](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/297)] +* Standardized data in storage and archive to Upper Case for consistency. [[#314](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/314)] + +### Enhancements to Existing Features + +* Added checking for existing stock in `StockValidator isValidStockId()` method. [[#19](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/19)] +* Included JUnit tests: + * `isValidStockId()`. [[#55](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/55)] + * `DateParserTest`. [[#90](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/90), [#125](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/125), [#304](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/304)] + * `DeleteStockCommandTest`. [[#304](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/304)] + * `ListOrderCommandTest`, `ArchiveOrderCommandTest`, `ArchivePrescriptionCommandTest`. [[#314](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/314)] +* Refactor all usage of `stockID` to `stockId` to maintain consistency with `orderId` and `prescriptionId`. [[#200](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/200)] +* Implemented `removeTime()` method in `DateParser`. [[#222](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/222)] + +### Documentation + +* [User Guide](../UserGuide.md) + * Added documentation for `deletestock`, `listorder`, `archiveorder`, `archiveprescription` commands + . [[#101](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/101), [#176](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/176), [#202](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/202), [#228](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/228)] + * Added documentation for `Data Storage`, `Data Editing`, `FAQ` sections in UG + . [[#202](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/202)] + * Added documentation for `Purpose` section in UG + . [[#294](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/294)] + + +* [Developer Guide](../DeveloperGuide.md) + * Section on `Storage` component + . [[#282](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/282)] + * Sections on `DeleteStockCommand`, `ArchivePrescriptionCommand`, `ArchiveOrderCommand` + . [[#219](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/219), [#274](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/274)] + * Value Proposition & Non Functional Requirements + . [[#266](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/266)] + * Instructions for Automated Testing + . [[#317](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/317)] + +### Team-Based + +* Attended all team meetings +* Documentation of the Purpose & FAQ in UG. +* Documentation of Value Proposition & NFRs & Automated Testing in DG. +* Providing feedback and communication with TA for advice on sequence diagrams in DG. +* Github pages emoji plugin. +* Pull Requests reviewed with non-trivial review comments + . [[#45](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/45), [#99](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/99), [#204](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/204), [#211](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/211), [#280](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/280), [#316](https://github.com/AY2122S1-CS2113T-T10-1/tp/pull/316)] + +### Community + +* Reported bugs and suggestions for other teams in the class [[#1](https://github.com/RemusTeo/ped/issues/1), [#2](https://github.com/RemusTeo/ped/issues/2), [#3](https://github.com/RemusTeo/ped/issues/3), [#4](https://github.com/RemusTeo/ped/issues/4), [#5](https://github.com/RemusTeo/ped/issues/5), [#6](https://github.com/RemusTeo/ped/issues/6), [#7](https://github.com/RemusTeo/ped/issues/7), [#8](https://github.com/RemusTeo/ped/issues/8)] \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..535d7121f2 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: MediVault + diff --git a/src/main/java/MediVault.java b/src/main/java/MediVault.java new file mode 100644 index 0000000000..6cfc677529 --- /dev/null +++ b/src/main/java/MediVault.java @@ -0,0 +1,83 @@ +import command.Command; +import command.CommandList; +import errors.InvalidCommandException; +import inventory.Medicine; +import utilities.parser.CommandParser; +import utilities.parser.Mode; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import static utilities.parser.Mode.ORDER; +import static utilities.parser.Mode.PRESCRIPTION; +import static utilities.parser.Mode.STOCK; + +//@@author alvintan01 + +/** + * Helps to start the application, and initialise all variables. + * It will continuously prompt for input from the user until "EXIT" is received. + */ +public class MediVault { + private static Logger logger = Logger.getLogger("MediVault"); + private Mode mode = Mode.STOCK; + + public MediVault() { + ArrayList medicines = Medicine.getInstance(); + Storage storage = Storage.getInstance(); + medicines.addAll(storage.loadData()); + logger.log(Level.INFO, "All variables are initialised."); + } + + public static void main(String[] args) { + LogManager.getLogManager().reset(); + logger.log(Level.INFO, "MediVault is starting up"); + new MediVault().run(); + } + + /** + * Prompts input from user and processes it indefinitely until "EXIT" is received. + */ + private void run() { + Ui ui = Ui.getInstance(); + ui.printWelcomeMessage(); + CommandParser commandParser = new CommandParser(); + + String userInput = ""; + + // Loops till exit is received + while (true) { + System.out.print("[" + mode + "] > "); + // Reads user input + userInput = ui.getInput(); + try { + String[] userCommand = commandParser.parseCommand(userInput); + String commandString = userCommand[0]; + String commandParameters = userCommand[1]; + + // Check is user is changing modes + if (commandString.equalsIgnoreCase(STOCK.name()) || commandString.equalsIgnoreCase(PRESCRIPTION.name()) + || commandString.equalsIgnoreCase(ORDER.name())) { + mode = commandParser.changeMode(ui, commandString, mode); + continue; + } + + Command command = commandParser.processCommand(commandString, commandParameters, mode); + command.execute(); + + if (commandString.equals(CommandList.EXIT)) { // User entered exit + break; + } + } catch (InvalidCommandException e) { + // Invalid Command + ui.printInvalidCommandMessage(); + logger.log(Level.WARNING, "An invalid command was entered!"); + } + } + logger.log(Level.INFO, "MediVault is shutting down"); + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..6ad3847e8d --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,12 @@ +package command; + +import java.util.LinkedHashMap; + +/** + * Represents the generic command. Helps to declare the abstract methods. It is inherited by all commands. + */ +public abstract class Command { + protected LinkedHashMap parameters; + + public abstract void execute(); +} diff --git a/src/main/java/command/CommandList.java b/src/main/java/command/CommandList.java new file mode 100644 index 0000000000..ab8509eb38 --- /dev/null +++ b/src/main/java/command/CommandList.java @@ -0,0 +1,31 @@ +package command; + +/** + * Represents all the commands available in the application. + */ +public class CommandList { + public static final String ADD = "add"; + public static final String ADD_PRESCRIPTION = "addprescription"; + public static final String ADD_STOCK = "addstock"; + public static final String ADD_ORDER = "addorder"; + public static final String ARCHIVE = "archive"; + public static final String ARCHIVE_ORDER = "archiveorder"; + public static final String ARCHIVE_PRESCRIPTION = "archiveprescription"; + public static final String DELETE = "delete"; + public static final String DELETE_PRESCRIPTION = "deleteprescription"; + public static final String DELETE_STOCK = "deletestock"; + public static final String DELETE_ORDER = "deleteorder"; + public static final String EXIT = "exit"; + public static final String HELP = "help"; + public static final String LIST = "list"; + public static final String LIST_PRESCRIPTION = "listprescription"; + public static final String LIST_STOCK = "liststock"; + public static final String LIST_ORDER = "listorder"; + public static final String PURGE = "purge"; + public static final String RECEIVE = "receive"; + public static final String RECEIVE_ORDER = "receiveorder"; + public static final String UPDATE = "update"; + public static final String UPDATE_STOCK = "updatestock"; + public static final String UPDATE_PRESCRIPTION = "updateprescription"; + public static final String UPDATE_ORDER = "updateorder"; +} diff --git a/src/main/java/command/CommandParameters.java b/src/main/java/command/CommandParameters.java new file mode 100644 index 0000000000..95bb39a7f6 --- /dev/null +++ b/src/main/java/command/CommandParameters.java @@ -0,0 +1,23 @@ +package command; + +/** + * Represents all the commands parameters used in the application. + */ +public class CommandParameters { + public static final String CUSTOMER_ID = "c"; + public static final String DATE = "d"; + public static final String DESCRIPTION = "d"; + public static final String EXPIRING = "expiring"; + public static final String EXPIRY_DATE = "e"; + public static final String ID = "i"; + public static final String MAX_QUANTITY = "m"; + public static final String NAME = "n"; + public static final String PRICE = "p"; + public static final String LOW = "low"; + public static final String QUANTITY = "q"; + public static final String REVERSED_SORT = "rsort"; + public static final String SORT = "sort"; + public static final String STAFF = "s"; + public static final String STATUS = "s"; + public static final String STOCK_ID = "sid"; +} diff --git a/src/main/java/command/CommandSyntax.java b/src/main/java/command/CommandSyntax.java new file mode 100644 index 0000000000..b5a88affbb --- /dev/null +++ b/src/main/java/command/CommandSyntax.java @@ -0,0 +1,64 @@ +package command; + +/** + * Contains all the valid command syntax accepted by the application. Also contains methods to validate if the + * parameter and its value is valid for a given command. + */ +public class CommandSyntax { + private String commandName; + private String commandSyntax; + public static final String COMMAND = "COMMAND"; + public static final String COMMAND_SYNTAX = "COMMAND SYNTAX"; + public static final String[] COLUMNS = {COMMAND, COMMAND_SYNTAX}; + public static final int NO_OF_COLUMNS = 2; + + public static final String ADD_PRESCRIPTION_COMMAND = "addprescription n/NAME q/QUANTITY c/CUSTOMER_ID " + + "s/STAFF_NAME"; + public static final String ADD_STOCK_COMMAND = "addstock n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE " + + "(d/DESCRIPTION m/MAX_QUANTITY)"; + public static final String ADD_ORDER_COMMAND = "addorder n/NAME q/QUANTITY {d/DATE}"; + public static final String ARCHIVE_PRESCRIPTION_COMMAND = "archiveprescription d/DATE"; + public static final String ARCHIVE_ORDER_COMMAND = "archiveorder d/DATE"; + public static final String DELETE_STOCK_COMMAND = "deletestock [i/ID expiring/EXPIRY_DATE]"; + public static final String DELETE_ORDER_COMMAND = "deleteorder i/ID"; + public static final String DELETE_PRESCRIPTION_COMMAND = "deleteprescription i/ID"; + public static final String EXIT_COMMAND = "exit"; + public static final String HELP_COMMAND = "help"; + public static final String LIST_PRESCRIPTION_COMMAND = "listprescription {i/ID n/NAME q/QUANTITY c/CUSTOMER_ID " + + "d/DATE s/STAFF_NAME sid/STOCK_ID sort/COLUMN_NAME rsort/COLUMN_NAME}"; + public static final String LIST_ORDER_COMMAND = "listorder {i/ID n/NAME q/QUANTITY d/DATE s/STATUS " + + "sort/COLUMN_NAME rsort/COLUMN_NAME}"; + public static final String LIST_STOCK_COMMAND = "liststock {i/ID n/NAME p/PRICE q/QUANTITY" + + "low/LESS_THAN_OR_EQUAL_QUANTITY e/EXPIRY_DATE expiring/LESS_THAN_OR_EQUAL_EXPIRY_DATE " + + "d/DESCRIPTION m/MAX_QUANTITY sort/COLUMN_NAME rsort/COLUMN_NAME}"; + public static final String PURGE_COMMAND = "purge"; + public static final String RECEIVE_ORDER_COMMAND = "receiveorder i/ID p/PRICE e/EXPIRY_DATE (d/DESCRIPTION " + + "m/MAX_QUANTITY)"; + public static final String UPDATE_PRESCRIPTION_COMMAND = "updateprescription i/ID [n/NAME q/QUANTITY c/CUSTOMER_ID " + + "d/DATE s/STAFF_NAME]"; + public static final String UPDATE_ORDER_COMMAND = "updateorder i/ID [n/NAME q/QUANTITY d/DATE]"; + public static final String UPDATE_STOCK_COMMAND = "updatestock i/ID [n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE " + + "d/DESCRIPTION m/MAX_QUANTITY]"; + + public CommandSyntax(String commandName, String commandSyntax) { + this.commandName = commandName; + this.commandSyntax = commandSyntax; + } + + public String getCommandName() { + return commandName; + } + + public void setCommandName(String commandName) { + this.commandName = commandName; + } + + public String getCommandSyntax() { + return commandSyntax; + } + + public void setCommandSyntax(String commandSyntax) { + this.commandSyntax = commandSyntax; + } + +} diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java new file mode 100644 index 0000000000..52f728ea7f --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,25 @@ +package command; + +import inventory.Medicine; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.util.ArrayList; + +//@@author alvintan01 + +/** + * Helps to process the exit command and prints the exit message. + */ +public class ExitCommand extends Command { + + @Override + public void execute() { + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + Storage storage = Storage.getInstance(); + + storage.saveData(medicines); + ui.printExit(); + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..9061b44d5b --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,45 @@ +package command; + +import utilities.ui.Ui; + +import java.util.ArrayList; + +//@@author alvintan01 + +/** + * Display help message containing command usage information. + */ +public class HelpCommand extends Command { + + @Override + public void execute() { + ArrayList commandSyntaxes = new ArrayList<>(); + commandSyntaxes.add(new CommandSyntax(CommandList.ADD_STOCK, CommandSyntax.ADD_STOCK_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.DELETE_STOCK, CommandSyntax.DELETE_STOCK_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.UPDATE_STOCK, CommandSyntax.UPDATE_STOCK_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.LIST_STOCK, CommandSyntax.LIST_STOCK_COMMAND)); + + commandSyntaxes.add(new CommandSyntax(CommandList.ADD_PRESCRIPTION, CommandSyntax.ADD_PRESCRIPTION_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.DELETE_PRESCRIPTION, + CommandSyntax.DELETE_PRESCRIPTION_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.UPDATE_PRESCRIPTION, + CommandSyntax.UPDATE_PRESCRIPTION_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.LIST_PRESCRIPTION, CommandSyntax.LIST_PRESCRIPTION_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.ARCHIVE_PRESCRIPTION, + CommandSyntax.ARCHIVE_PRESCRIPTION_COMMAND)); + + commandSyntaxes.add(new CommandSyntax(CommandList.ADD_ORDER, CommandSyntax.ADD_ORDER_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.DELETE_ORDER, CommandSyntax.DELETE_ORDER_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.UPDATE_ORDER, CommandSyntax.UPDATE_ORDER_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.LIST_ORDER, CommandSyntax.LIST_ORDER_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.ARCHIVE_ORDER, CommandSyntax.ARCHIVE_ORDER_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.RECEIVE_ORDER, CommandSyntax.RECEIVE_ORDER_COMMAND)); + + commandSyntaxes.add(new CommandSyntax(CommandList.PURGE, CommandSyntax.PURGE_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.HELP, CommandSyntax.HELP_COMMAND)); + commandSyntaxes.add(new CommandSyntax(CommandList.EXIT, CommandSyntax.EXIT_COMMAND)); + + Ui ui = Ui.getInstance(); + ui.printHelpMessage(commandSyntaxes); + } +} diff --git a/src/main/java/command/PurgeCommand.java b/src/main/java/command/PurgeCommand.java new file mode 100644 index 0000000000..29cdcf24bb --- /dev/null +++ b/src/main/java/command/PurgeCommand.java @@ -0,0 +1,39 @@ +package command; + +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.Scanner; + +//@@author alvintan01 + +/** + * Helps to process the purge command and prompts the user for confirmation. + */ +public class PurgeCommand extends Command { + @Override + public void execute() { + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + Storage storage = Storage.getInstance(); + + ui.print("Are you sure you want to delete all data? (Y/N)"); + Scanner in = new Scanner(System.in); + if ("Y".equals(in.nextLine())) { + medicines.clear(); + // Reset the IDs for Stock, Prescription and Orders + Stock.setStockCount(0); + Prescription.setPrescriptionCount(0); + Order.setOrderCount(0); + ui.print("All data has been cleared!"); + storage.saveData(medicines); + } else { + ui.print("Purge aborted!"); + } + } +} diff --git a/src/main/java/command/order/AddOrderCommand.java b/src/main/java/command/order/AddOrderCommand.java new file mode 100644 index 0000000000..cbfe6c339b --- /dev/null +++ b/src/main/java/command/order/AddOrderCommand.java @@ -0,0 +1,183 @@ +package command.order; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Order; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.parser.OrderManager; +import utilities.parser.OrderValidator; +import utilities.parser.StockManager; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author jiangweichen835 + +/** + * Add order for medication based on user input. + * User input include medication name, quantity and the order date. + */ +public class AddOrderCommand extends Command { + private static Logger logger = Logger.getLogger("AddOrder"); + + public AddOrderCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start addition of orders"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + String[] requiredParameters = {CommandParameters.NAME, CommandParameters.QUANTITY}; + String[] optionalParameter = {CommandParameters.DATE}; + + OrderValidator orderValidator = new OrderValidator(); + if (checkValidParameterValues(ui, parameters, medicines, requiredParameters, optionalParameter, + orderValidator)) { + return; + } + + boolean nameExistsInOrder = false; + boolean nameExistsInStock = false; + String nameToAdd = parameters.get(CommandParameters.NAME); + String quantityToAdd = parameters.get(CommandParameters.QUANTITY); + int orderQuantity = Integer.parseInt(quantityToAdd); + String dateToAdd = parameters.get(CommandParameters.DATE); + int maxQuantity = Integer.MAX_VALUE; + + if (orderQuantity == 0) { + ui.print("Order Quantity cannot be 0."); + return; + } + + if (parameters.containsKey(CommandParameters.NAME)) { + nameToAdd = parameters.get(CommandParameters.NAME); + for (Medicine medicine : medicines) { + if (medicine instanceof Order && medicine.getMedicineName().equalsIgnoreCase(nameToAdd)) { + nameExistsInOrder = true; + break; + } + } + } + + if (parameters.containsKey(CommandParameters.NAME)) { + nameToAdd = parameters.get(CommandParameters.NAME); + for (Medicine medicine : medicines) { + if (medicine instanceof Stock && medicine.getMedicineName().equalsIgnoreCase(nameToAdd) + && !((Stock) medicine).isDeleted()) { + nameExistsInStock = true; + break; + } + } + } + + if (nameExistsInOrder && !nameExistsInStock) { + if (orderQuantity < maxQuantity) { + addOrder(ui, medicines, nameToAdd, orderQuantity, addDate(dateToAdd)); + } + } else if (nameExistsInOrder && nameExistsInStock) { + int existingOrdersQuantity = OrderManager.getTotalOrderQuantity(medicines, nameToAdd); + int existingStockQuantity = StockManager.getTotalStockQuantity(medicines, nameToAdd); + int totalQuantity = existingStockQuantity + existingOrdersQuantity; + maxQuantity = StockManager.getMaxStockQuantity(medicines, nameToAdd); + + if (orderQuantity + totalQuantity <= maxQuantity) { + addOrder(ui, medicines, nameToAdd, orderQuantity, addDate(dateToAdd)); + } else { + ui.print("Unable to add order as total order quantity exceeds maximum stock quantity of " + + maxQuantity + ".\nExisting quantity in stock: " + existingStockQuantity + + "\nPending order quantity: " + existingOrdersQuantity); + } + } else if (!nameExistsInOrder && nameExistsInStock) { + int existingStockQuantity = StockManager.getTotalStockQuantity(medicines, nameToAdd); + maxQuantity = StockManager.getMaxStockQuantity(medicines, nameToAdd); + if (orderQuantity + existingStockQuantity <= maxQuantity) { + addOrder(ui, medicines, nameToAdd, orderQuantity, addDate(dateToAdd)); + } else { + ui.print("Unable to add order as total order quantity exceeds maximum stock quantity of " + + maxQuantity + ".\nExisting quantity in stock: " + existingStockQuantity); + } + } else { + addOrder(ui, medicines, nameToAdd, orderQuantity, addDate(dateToAdd)); + } + } + + /** + * Add order based on user input. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param name Medication name to order. + * @param quantity Quantity of medication to order. + * @param date Order date. + */ + private void addOrder(Ui ui, ArrayList medicines, String name, int quantity, Date date) { + Order order = new Order(name, quantity, date); + medicines.add(order); + ui.print("Order added: " + name); + ui.printOrder(order); + + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + logger.log(Level.INFO, "Successful addition of order"); + } + + /** + * Add date based on user input. + * + * @param dateToAdd Order date input by user (check if it is in correct date format). + * @return Default date or order date. + */ + private Date addDate(String dateToAdd) { + if (dateToAdd == null) { + Date defaultDate = null; + defaultDate = new Date(); + return defaultDate; + } + + try { + Date orderDate = null; + orderDate = DateParser.stringToDate(dateToAdd); + return orderDate; + } catch (ParseException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Checks if user inputs are valid. + * + * @param ui Reference to the UI object to print messages. + * @param parameters The parameter that is not found. + * @param medicines List of all medicines. + * @param requiredParameters The required parameters to check. + * @param optionalParameters The optional parameters to check. + * @param orderValidator Reference to OrderValidator object. + * @return Boolean value indicating if parameter and parameter values are valid. + */ + private boolean checkValidParameterValues(Ui ui, LinkedHashMap parameters, + ArrayList medicines, String[] requiredParameters, + String[] optionalParameters, OrderValidator orderValidator) { + boolean isInvalidInput = orderValidator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.ADD_ORDER_COMMAND, + false, orderValidator); + + if (isInvalidInput) { + logger.log(Level.WARNING, "Invalid parameter or value specified by user"); + return true; + } + return false; + } +} diff --git a/src/main/java/command/order/ArchiveOrderCommand.java b/src/main/java/command/order/ArchiveOrderCommand.java new file mode 100644 index 0000000000..35baf77976 --- /dev/null +++ b/src/main/java/command/order/ArchiveOrderCommand.java @@ -0,0 +1,106 @@ +package command.order; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Order; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.OrderValidator; +import utilities.ui.Ui; +import utilities.storage.Storage; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author RemusTeo + +/** + * Archive orders based on user input given date. + */ +public class ArchiveOrderCommand extends Command { + private static Logger logger = Logger.getLogger("ArchiveOrder"); + + public ArchiveOrderCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start archiving of order"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {CommandParameters.DATE}; + String[] optionalParameters = {}; + + MedicineValidator validator = new OrderValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.ARCHIVE_ORDER_COMMAND, true, validator); + if (isInvalidInput) { + logger.log(Level.INFO, "Unsuccessful archive of order"); + return; + } + + Date orderArchiveDate = null; + String orderArchiveDateStr = parameters.get(CommandParameters.DATE); + try { + orderArchiveDate = DateParser.stringToDate(orderArchiveDateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + + ArrayList filteredOrders = ordersToArchive(medicines, orderArchiveDate); + removeFromOrders(medicines, filteredOrders); + + Storage storage = Storage.getInstance(); + storage.archiveData(filteredOrders); + storage.saveData(medicines); + ui.print("Archived " + filteredOrders.size() + " delivered orders from " + + DateParser.dateToString(orderArchiveDate)); + logger.log(Level.INFO, "Successful archive of order"); + } + + /** + * Checks through all orders and look for records that are DELIVERED and have order date <= specified date. + * + * @param medicines Arraylist of all medicines. + * @param orderArchiveDate Date that user specified to archive. + * @return Arraylist of orders that meet the archive requirements. + */ + private ArrayList ordersToArchive(ArrayList medicines, Date orderArchiveDate) { + ArrayList filteredOrders = new ArrayList<>(); + for (Medicine medicine : medicines) { + if (!(medicine instanceof Order)) { + continue; + } + Order order = (Order) medicine; + Date orderDate = DateParser.removeTime(order.getDate()); + if (order.getStatus().equalsIgnoreCase("DELIVERED")) { + if (orderDate.before(orderArchiveDate) || orderDate.equals(orderArchiveDate)) { + filteredOrders.add(order); + } + } + } + return filteredOrders; + } + + /** + * Removal of orders from order list after archive. + * + * @param medicines Arraylist of all medicines. + * @param filteredOrders Arraylist of orders that meet the archive requirements. + */ + private void removeFromOrders(ArrayList medicines, ArrayList filteredOrders) { + for (Medicine medicine : filteredOrders) { + medicines.remove(medicine); + } + } +} + diff --git a/src/main/java/command/order/DeleteOrderCommand.java b/src/main/java/command/order/DeleteOrderCommand.java new file mode 100644 index 0000000000..f2033af416 --- /dev/null +++ b/src/main/java/command/order/DeleteOrderCommand.java @@ -0,0 +1,94 @@ +package command.order; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Order; +import utilities.parser.MedicineValidator; +import utilities.parser.OrderValidator; +import utilities.ui.Ui; +import utilities.storage.Storage; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author deonchung + +/** + * Delete order based on user input given order id. + */ +public class DeleteOrderCommand extends Command { + private static Logger logger = Logger.getLogger("Delete Order"); + + public DeleteOrderCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start deletion of order"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + String orderIdToDelete = parameters.get(CommandParameters.ID); + + if (!isValidOrderParameters(ui, medicines)) { + return; + } + + int orderId = Integer.parseInt(orderIdToDelete); + + assert orderId <= Order.getOrderCount() : "order Id should not exceed max order count"; + + for (Medicine medicine : medicines) { + + if (!(medicine instanceof Order)) { + continue; + } + + Order order = (Order) medicine; + + if (order.getOrderId() == orderId) { + medicines.remove(order); + logger.log(Level.INFO, "Order id found and deleted"); + break; + } + } + + ui.print("Order deleted for Order ID " + orderId); + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + logger.log(Level.INFO, "Successful deletion of order"); + + } + + /** + * Check if parameters values for Order are valid and if Order ID exist. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of medicines + * @return Boolean Value indicating if parameters values for Order are valid and Order ID exist. + */ + private boolean isValidOrderParameters(Ui ui, ArrayList medicines) { + + MedicineValidator validator = new OrderValidator(); + + String[] requiredParameters = {CommandParameters.ID}; + String[] optionalParameters = {}; + + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.DELETE_ORDER_COMMAND, false, validator); + + if (isInvalidInput) { + logger.log(Level.WARNING, "Invalid parameter or value specified by user"); + logger.log(Level.INFO, "Unsuccessful deletion of order"); + return false; + } + + return true; + } +} + diff --git a/src/main/java/command/order/ListOrderCommand.java b/src/main/java/command/order/ListOrderCommand.java new file mode 100644 index 0000000000..16904d405e --- /dev/null +++ b/src/main/java/command/order/ListOrderCommand.java @@ -0,0 +1,120 @@ +package command.order; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Order; +import utilities.comparators.OrderComparator; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.OrderValidator; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +//@@author RemusTeo + +/** + * Helps to process the listorder command together with filters and sort. + */ +public class ListOrderCommand extends Command { + private static Logger logger = Logger.getLogger("ListOrder"); + + public ListOrderCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start listing of order records"); + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {}; + String[] optionalParameters = {CommandParameters.ID, CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.DATE, CommandParameters.STATUS, CommandParameters.SORT, + CommandParameters.REVERSED_SORT}; + + MedicineValidator validator = new OrderValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.LIST_ORDER_COMMAND, false, validator); + if (isInvalidInput) { + return; + } + + ArrayList filteredOrders = new ArrayList<>(); + + assert (filteredOrders != null) : "Array is not initialised"; + + for (Medicine medicine : medicines) { + if (medicine instanceof Order) { + filteredOrders.add((Order) medicine); + } + } + filteredOrders = filterOrders(parameters, filteredOrders); + + ui.printOrders(filteredOrders); + logger.log(Level.INFO, "Successful listing of order"); + } + + + /** + * Helps to filter order records based on the user's input. + * + * @param parameters HashMap Key-Value set for parameter and user specified parameter value. + * @param filteredOrders Arraylist of Order objects. + * @return Arraylist of filtered Order objects based on the user's parameters values. + */ + private ArrayList filterOrders(LinkedHashMap parameters, ArrayList filteredOrders) { + for (String parameter : parameters.keySet()) { + String parameterValue = parameters.get(parameter); + switch (parameter) { + case CommandParameters.ID: + filteredOrders = (ArrayList) filteredOrders.stream() + .filter((m) -> (m).getOrderId() == Integer.parseInt(parameterValue)) + .collect(Collectors.toList()); + break; + case CommandParameters.NAME: + filteredOrders = (ArrayList) filteredOrders.stream() + .filter((m) -> (m.getMedicineName().toUpperCase()).contains(parameterValue.toUpperCase())) + .collect(Collectors.toList()); + break; + case CommandParameters.QUANTITY: + filteredOrders = (ArrayList) filteredOrders.stream().filter((m) -> + m.getQuantity() == Integer.parseInt(parameterValue)).collect(Collectors.toList()); + break; + case CommandParameters.DATE: + try { + Date date = DateParser.stringToDate(parameterValue); + filteredOrders = (ArrayList) filteredOrders.stream() + .filter((m) -> (m).getDate().equals(date)) + .collect(Collectors.toList()); + } catch (ParseException e) { + e.printStackTrace(); + } + break; + case CommandParameters.STATUS: + filteredOrders = (ArrayList) filteredOrders.stream() + .filter((m) -> (m.getStatus()).equalsIgnoreCase(parameterValue)) + .collect(Collectors.toList()); + break; + case CommandParameters.SORT: + filteredOrders.sort(new OrderComparator(parameterValue.toLowerCase(), false)); + break; + case CommandParameters.REVERSED_SORT: + filteredOrders.sort(new OrderComparator(parameterValue.toLowerCase(), true)); + break; + default: + return filteredOrders; + } + } + return filteredOrders; + } +} diff --git a/src/main/java/command/order/ReceiveOrderCommand.java b/src/main/java/command/order/ReceiveOrderCommand.java new file mode 100644 index 0000000000..3fa4efffa8 --- /dev/null +++ b/src/main/java/command/order/ReceiveOrderCommand.java @@ -0,0 +1,186 @@ +package command.order; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import command.stock.AddStockCommand; +import inventory.Medicine; +import inventory.Order; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.OrderValidator; +import utilities.parser.StockValidator; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author alvintan01 + +/** + * Helps to add an order to stocks and mark the order as delivered. + */ +public class ReceiveOrderCommand extends Command { + private static Logger logger = Logger.getLogger("ReceiveOrder"); + + public ReceiveOrderCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + if (!checkOrderIdExist(ui, medicines)) { + return; + } + int orderId = Integer.parseInt(parameters.get(CommandParameters.ID)); + parameters.remove(CommandParameters.ID); // Remove ID to prevent checks against stock ID + String name = ""; + Order existingOrder = null; + + for (Medicine medicine : medicines) { + if (medicine instanceof Order && ((Order) medicine).getOrderId() == orderId + && !((Order) medicine).isDelivered()) { + existingOrder = (Order) medicine; // Found existing order, add to parameters + name = existingOrder.getMedicineName(); + parameters.put(CommandParameters.NAME, name); + parameters.put(CommandParameters.QUANTITY, String.valueOf(existingOrder.getQuantity())); + break; + } + } + + if (!isStockParametersValid(ui, medicines, name)) { + return; + } + + assert (existingOrder != null) : "Order object is not initialised!"; + int currentQuantity = getCurrentQuantity(medicines, name); + + new AddStockCommand(parameters).execute(); // Add to stock + + int afterAddedQuantity = getCurrentQuantity(medicines, name); + + // Check if quantity increased + if (afterAddedQuantity > currentQuantity) { + existingOrder.setDelivered(); + } + } + + /** + * Check if same medication name and expiry date exist and returns the current quantity. + * + * @param medicines Arraylist of all medicines. + * @param name Medication name to be searched. + * @return Integer value indicating the current quantity. + */ + private int getCurrentQuantity(ArrayList medicines, String name) { + int currentQuantity = 0; + String expiryToAdd = parameters.get(CommandParameters.EXPIRY_DATE); + Date expiryDate = null; + try { + expiryDate = DateParser.stringToDate(expiryToAdd); + } catch (ParseException e) { + e.printStackTrace(); + } + for (Medicine medicine : medicines) { + if (medicine instanceof Stock && medicine.getMedicineName().equalsIgnoreCase(name) + && !((Stock) medicine).isDeleted() && ((Stock) medicine).getExpiry().equals(expiryDate)) { + currentQuantity = medicine.getQuantity(); + break; + } + } + return currentQuantity; + } + + /** + * Checks if a medication exists in stock. + * + * @param medicines Arraylist of all medicines. + * @param name Medication name to be searched. + * @return Boolean value indicating if the stock exists. + */ + private boolean checkStockExist(ArrayList medicines, String name) { + // Search if current medication exist in stock + for (Medicine medicine : medicines) { + if (medicine instanceof Stock && medicine.getMedicineName().equalsIgnoreCase(name) + && !((Stock) medicine).isDeleted()) { + return true; + } + } + return false; + } + + /** + * Checks if an order ID exists and is not delivered. + * + * @param ui Reference to UI object to print messages. + * @param medicines Arraylist of all medicines. + * @return Boolean value indicating if order ID is valid. + */ + private boolean checkOrderIdExist(Ui ui, ArrayList medicines) { + MedicineValidator validator = new OrderValidator(); + String[] orderRequiredParameters = {CommandParameters.ID}; + String[] optionalParameters = {}; + LinkedHashMap orderParameters = new LinkedHashMap<>(); + if (parameters.containsKey(CommandParameters.ID)) { + orderParameters.put(CommandParameters.ID, parameters.get(CommandParameters.ID)); + } + + boolean orderIdNotProvided = validator.containsInvalidParametersAndValues(ui, medicines, orderParameters, + orderRequiredParameters, optionalParameters, CommandSyntax.RECEIVE_ORDER_COMMAND, false, validator); + if (orderIdNotProvided) { + logger.log(Level.WARNING, "Order id is not specified by user!"); + return false; + } + + int orderId = Integer.parseInt(parameters.get(CommandParameters.ID)); + for (Medicine medicine : medicines) { + if (!(medicine instanceof Order)) { + continue; + } + Order order = (Order) medicine; + if (order.getOrderId() == orderId) { + if (order.isDelivered()) { + ui.print("Order is already delivered!"); + return false; + } else { + return true; + } + } + } + return false; + } + + /** + * Helps to ensure that the parameters values for Stock are valid. + * + * @param ui Reference to UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param name Medication name to be searched. + * @return Boolean value indicating if Stock parameters are valid. + */ + private boolean isStockParametersValid(Ui ui, ArrayList medicines, String name) { + MedicineValidator validator = new StockValidator(); + String[] requiredParameters = {CommandParameters.PRICE, CommandParameters.EXPIRY_DATE}; + String[] optionalParameters = {}; + if (!checkStockExist(medicines, name)) { + requiredParameters = new String[]{CommandParameters.PRICE, CommandParameters.EXPIRY_DATE, + CommandParameters.DESCRIPTION, CommandParameters.MAX_QUANTITY}; + } + + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.RECEIVE_ORDER_COMMAND, false, validator); + if (isInvalidInput) { + logger.log(Level.WARNING, "Invalid parameter or value specified by user"); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/command/order/UpdateOrderCommand.java b/src/main/java/command/order/UpdateOrderCommand.java new file mode 100644 index 0000000000..52458f1e42 --- /dev/null +++ b/src/main/java/command/order/UpdateOrderCommand.java @@ -0,0 +1,142 @@ +package command.order; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Order; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.OrderManager; +import utilities.parser.OrderValidator; +import utilities.parser.StockManager; +import utilities.parser.StockValidator; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author a-tph + +/** + * Update medication information based on user input given order id. + */ +public class UpdateOrderCommand extends Command { + private static Logger logger = Logger.getLogger("UpdateOrder"); + + public UpdateOrderCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start of UpdateOrder command execution."); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {CommandParameters.ID}; + String[] optionalParameters = {CommandParameters.NAME, CommandParameters.QUANTITY, CommandParameters.DATE}; + + MedicineValidator validator = new OrderValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.UPDATE_ORDER_COMMAND, true, validator); + if (isInvalidInput) { + return; + } + + Order order = OrderManager.extractOrderObject(parameters, medicines); + if (order.isDelivered()) { + ui.print("Update aborted! Unable to update order that has been delivered"); + return; + } + + int maxQuantity = StockManager.getMaxStockQuantity(medicines, order.getMedicineName()); + assert maxQuantity >= 0 : "Max quantity must not be less than 0"; + boolean existName = maxQuantity > 0; + boolean existQuantityParam = parameters.containsKey(CommandParameters.QUANTITY); + + if (existName && existQuantityParam) { + boolean isValidQuantity = checkUpdateQuantity(ui, medicines, order, maxQuantity); + if (!isValidQuantity) { + return; + } + } + + ArrayList filteredOrders = OrderManager.getFilteredOrdersByName(medicines, order.getMedicineName()); + + // Default value for updating all affected rows + int rowsAffected = filteredOrders.size(); + if (!parameters.containsKey(CommandParameters.NAME)) { + filteredOrders.clear(); + filteredOrders.add(order); + rowsAffected = filteredOrders.size(); + } + + setUpdatesByOrderId(filteredOrders, order); + ui.print("Updated! Number of rows affected: " + rowsAffected); + ui.printOrders(filteredOrders); + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + logger.log(Level.INFO, "End of UpdateOrder command execution."); + } + + /** + * Checks if the updated order quantity exceeds the maximum quantity. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param order Order object to be updated. + * @param maxQuantity Maximum quantity for the provided stock. + * @return Boolean true if quantity given can be updated. + */ + private boolean checkUpdateQuantity(Ui ui, ArrayList medicines, Order order, int maxQuantity) { + int totalQuantity = OrderManager.getTotalOrderQuantity(medicines, order.getMedicineName()); + int orderQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + int actualTotalQuantity = totalQuantity - order.getQuantity() + orderQuantity; + StockValidator stockValidator = new StockValidator(); + boolean isValidQuantity = stockValidator.quantityValidityChecker(ui, actualTotalQuantity, maxQuantity); + if (!isValidQuantity) { + return false; + } + return true; + } + + /** + * Update values provided by user for a given order id. + * + * @param filteredOrders Arraylist of filtered medicine orders. + * @param order Order object of the given order id. + */ + private void setUpdatesByOrderId(ArrayList filteredOrders, Order order) { + logger.log(Level.INFO, "Attempt to update order information."); + for (String parameter : parameters.keySet()) { + String parameterValue = parameters.get(parameter); + switch (parameter) { + case CommandParameters.NAME: + for (Order targetOrder : filteredOrders) { + targetOrder.setMedicineName(parameterValue); + } + break; + case CommandParameters.QUANTITY: + order.setQuantity(Integer.parseInt(parameterValue)); + break; + case CommandParameters.DATE: + try { + order.setDate(DateParser.stringToDate(parameterValue)); + } catch (ParseException e) { + e.printStackTrace(); + } + break; + default: + break; + } + } + logger.log(Level.INFO, "Updated order information with given user input."); + } + +} diff --git a/src/main/java/command/prescription/AddPrescriptionCommand.java b/src/main/java/command/prescription/AddPrescriptionCommand.java new file mode 100644 index 0000000000..3a1ed9c249 --- /dev/null +++ b/src/main/java/command/prescription/AddPrescriptionCommand.java @@ -0,0 +1,198 @@ +package command.prescription; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Prescription; +import inventory.Medicine; +import inventory.Stock; +import utilities.parser.MedicineValidator; +import utilities.parser.PrescriptionValidator; +import utilities.parser.DateParser; +import utilities.parser.PrescriptionManager; +import utilities.parser.StockManager; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; + +//@@author deonchung + +/** + * Prescribes medication based on user input. + * User input includes medication name, quantity to prescribe, Customer's NRIC and Staff name. + */ +public class AddPrescriptionCommand extends Command { + + public AddPrescriptionCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String medicationName = parameters.get(CommandParameters.NAME); + String quantity = parameters.get(CommandParameters.QUANTITY); + String customerId = parameters.get(CommandParameters.CUSTOMER_ID); + String staffName = parameters.get(CommandParameters.STAFF); + + String[] requiredParameters = {CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.CUSTOMER_ID, CommandParameters.STAFF}; + String[] optionalParameters = {}; + + MedicineValidator validator = new PrescriptionValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.ADD_PRESCRIPTION_COMMAND, false, validator); + if (isInvalidInput) { + return; + } + + int prescriptionQuantity = Integer.parseInt(quantity); + int quantityToPrescribe = prescriptionQuantity; + + if (quantityToPrescribe == 0) { + ui.print("Prescription Quantity cannot be 0."); + return; + } + + ArrayList filteredStocks = StockManager.getFilteredStocksByName(medicines, medicationName); + + if (filteredStocks.isEmpty()) { + ui.print("Medicine not available!"); + return; + } + + Date prescribeDate = new Date(); //prescribe date will be today's date + String prescribeDateString = DateParser.dateToString(prescribeDate); + + filteredStocks.sort(new utilities.comparators.StockComparator(CommandParameters.EXPIRY_DATE, false)); + + if (checkExpiredMedication(ui, filteredStocks, prescriptionQuantity)) { + return; + } + + int totalStock = PrescriptionManager.getNotExpiredStockQuantity(medicines, medicationName, prescribeDate); + + if (prescriptionQuantity > totalStock) { + ui.print("Unable to Prescribe! Prescription quantity is more than stock available!"); + ui.print("Prescription quantity: " + prescriptionQuantity + " Stock available: " + totalStock); + return; + } + + for (Stock stock : filteredStocks) { + int existingQuantity = stock.getQuantity(); + int existingId = stock.getStockId(); + Date existingExpiry = stock.getExpiry(); + String expiryString = DateParser.dateToString(existingExpiry); + + if (existingExpiry.after(prescribeDate) || prescribeDateString.equals(expiryString)) { + + int setStockValue = 0; + + if (existingQuantity == quantityToPrescribe) { + prescribe(ui, medicines, medicationName, customerId, staffName, existingQuantity, prescribeDate, + stock, existingId, existingExpiry, setStockValue); + return; + } + + if (existingQuantity > quantityToPrescribe) { + setStockValue = existingQuantity - quantityToPrescribe; + prescribe(ui, medicines, medicationName, customerId, staffName, quantityToPrescribe, prescribeDate, + stock, existingId, existingExpiry, setStockValue); + return; + } + + if (existingQuantity < prescriptionQuantity && existingQuantity != 0) { + quantityToPrescribe = quantityToPrescribe - existingQuantity; + prescribe(ui, medicines, medicationName, customerId, staffName, existingQuantity, prescribeDate, + stock, existingId, existingExpiry, setStockValue); + } + } + + } + + ui.print("Unable to Prescribe! Medicine has expired!"); + + } + + /** + * Check if non-expired medication exist. + * + * @param ui Reference to the UI object to print messages. + * @param filteredStocks List of stock sorted by expiry date. + * @param prescriptionQuantity Quantity to prescribe. + * @return Boolean Value indicating if expired medication exist. + */ + private boolean checkExpiredMedication(Ui ui, ArrayList filteredStocks, int prescriptionQuantity) { + boolean existNonExpiredMed = false; + boolean noStockLeft = false; + for (Stock stock : filteredStocks) { + Date expiryDate = stock.getExpiry(); + Date todayDate = new Date(); + + String todayDateString = DateParser.dateToString(todayDate); + String latestExpiryString = DateParser.dateToString(expiryDate); + + boolean isNotExpired = expiryDate.after(todayDate) || todayDateString.equals(latestExpiryString); + + if (isNotExpired && stock.getQuantity() != 0 && !(stock.isDeleted())) { + existNonExpiredMed = true; + } + if (isNotExpired && stock.getQuantity() == 0 && !(stock.isDeleted())) { + noStockLeft = true; + } + } + + if (noStockLeft) { + ui.print("Unable to Prescribe! Prescription quantity is more than stock available!"); + ui.print("Prescription quantity: " + prescriptionQuantity + " Stock available: 0"); + return true; + } + + if (!existNonExpiredMed) { + ui.print("Unable to Prescribe! Medication has expired!"); + return true; + } + return false; + } + + /** + * Change the stock quantity based on prescription quantity. Add prescribed medication to prescription list. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param medicationName Medication to prescribe. + * @param customerId Customer ID whom medicine will be prescribed to. + * @param staffName Staff who prescribe the medication. + * @param quantityToPrescribe Quantity of medication to prescribe. + * @param prescribeDate Date which medication is prescribed + * @param stock Stock object of the given stock id. + * @param existingId Existing id of the stock object. + * @param existingExpiry Existing expiry of the stock object. + * @param setStockValue Stock quantity to set to after prescription. + */ + private void prescribe(Ui ui, ArrayList medicines, String medicationName, String customerId, + String staffName, int quantityToPrescribe, Date prescribeDate, Stock stock, + int existingId, Date existingExpiry, int setStockValue) { + String expiry = DateParser.dateToString(existingExpiry); + stock.setQuantity(setStockValue); + Prescription prescription = new Prescription(medicationName, quantityToPrescribe, customerId, prescribeDate, + staffName, existingId); + medicines.add(prescription); + ui.print("Prescribed:" + medicationName + " Quantity:" + quantityToPrescribe + " Expiry " + + "Date:" + expiry); + ui.printPrescription(prescription); + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + } + +} + + + + + diff --git a/src/main/java/command/prescription/ArchivePrescriptionCommand.java b/src/main/java/command/prescription/ArchivePrescriptionCommand.java new file mode 100644 index 0000000000..cd891b27a3 --- /dev/null +++ b/src/main/java/command/prescription/ArchivePrescriptionCommand.java @@ -0,0 +1,106 @@ +package command.prescription; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Prescription; +import inventory.Medicine; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.OrderValidator; +import utilities.parser.PrescriptionValidator; +import utilities.ui.Ui; +import utilities.storage.Storage; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author RemusTeo + +/** + * Archive prescription based on user input given date. + */ +public class ArchivePrescriptionCommand extends Command { + private static Logger logger = Logger.getLogger("ArchivePrescription"); + + public ArchivePrescriptionCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start archiving of prescription"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {CommandParameters.DATE}; + String[] optionalParameters = {}; + + MedicineValidator validator = new PrescriptionValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.ARCHIVE_PRESCRIPTION_COMMAND, true, validator); + if (isInvalidInput) { + logger.log(Level.INFO, "Unsuccessful archive of prescription"); + return; + } + + Date prescriptionArchiveDate = null; + String prescriptionArchiveDateStr = parameters.get(CommandParameters.DATE); + try { + prescriptionArchiveDate = DateParser.stringToDate(prescriptionArchiveDateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + + ArrayList filteredPrescriptions = prescriptionsToArchive(medicines, prescriptionArchiveDate); + removeFromPrescriptions(medicines, filteredPrescriptions); + + Storage storage = Storage.getInstance(); + storage.archiveData(filteredPrescriptions); + storage.saveData(medicines); + ui.print("Archived " + filteredPrescriptions.size() + " prescriptions from " + + DateParser.dateToString(prescriptionArchiveDate)); + logger.log(Level.INFO, "Successful archive of prescriptions"); + } + + /** + * Checks through all prescriptions and look for records that have prescription date <= specified date. + * + * @param medicines Arraylist of all medicines. + * @param prescriptionArchiveDate Date that user specified to archive. + * @return Arraylist of prescriptions that meet the archive requirements. + */ + private ArrayList prescriptionsToArchive(ArrayList medicines, Date prescriptionArchiveDate) { + ArrayList filteredPrescriptions = new ArrayList<>(); + for (Medicine medicine : medicines) { + if (!(medicine instanceof Prescription)) { + continue; + } + Prescription prescription = (Prescription) medicine; + Date prescriptionDate = DateParser.removeTime(prescription.getDate()); + if (prescriptionDate.before(prescriptionArchiveDate) + || prescriptionDate.equals(prescriptionArchiveDate)) { + filteredPrescriptions.add(prescription); + } + } + return filteredPrescriptions; + } + + /** + * Removal of prescriptions from prescription list after archive. + * + * @param medicines Arraylist of all medicines. + * @param filteredPrescriptions Arraylist of prescriptions that meet the archive requirements. + */ + private void removeFromPrescriptions(ArrayList medicines, ArrayList filteredPrescriptions) { + for (Medicine medicine : filteredPrescriptions) { + medicines.remove(medicine); + } + } +} + diff --git a/src/main/java/command/prescription/DeletePrescriptionCommand.java b/src/main/java/command/prescription/DeletePrescriptionCommand.java new file mode 100644 index 0000000000..d413676875 --- /dev/null +++ b/src/main/java/command/prescription/DeletePrescriptionCommand.java @@ -0,0 +1,139 @@ +package command.prescription; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Prescription; +import inventory.Medicine; +import inventory.Stock; +import utilities.parser.MedicineValidator; +import utilities.parser.PrescriptionValidator; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author deonchung + +/** + * Delete prescription based on user input given prescription id. + */ +public class DeletePrescriptionCommand extends Command { + private static Logger logger = Logger.getLogger("DeletePrescription"); + + public DeletePrescriptionCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start deletion of prescription"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String prescriptionIdToDelete = parameters.get(CommandParameters.ID); + + if (!isValidPrescriptionParameters(ui, medicines)) { + return; + } + + int prescriptionId = Integer.parseInt(prescriptionIdToDelete); + + assert prescriptionId <= Prescription.getPrescriptionCount() : "Prescription ID should not exceed max " + + "prescription count"; + + int stockIdToPrescribe; + int prescribeQuantity; + + for (Medicine medicine : medicines) { + if (!(medicine instanceof Prescription)) { + continue; + } + + Prescription prescription = (Prescription) medicine; + + if (prescription.getPrescriptionId() == prescriptionId) { + stockIdToPrescribe = prescription.getStockId(); + prescribeQuantity = prescription.getQuantity(); + + if (setStockQuantity(ui, medicines, stockIdToPrescribe, prescribeQuantity)) { + return; + } + + medicines.remove(prescription); + ui.print("Prescription deleted for Prescription ID " + prescriptionId); + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + logger.log(Level.INFO, "Successful deletion of Prescription"); + return; + + } + } + } + + /** + * Check if parameters values for Prescription are valid and if Prescription ID exist. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of medicines. + * @return Boolean Value indicating if parameters values for Prescription are valid and Prescription ID exist. + */ + private boolean isValidPrescriptionParameters(Ui ui, ArrayList medicines) { + + MedicineValidator validator = new PrescriptionValidator(); + String[] requiredParameters = {CommandParameters.ID}; + String[] optionalParameters = {}; + + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.DELETE_PRESCRIPTION_COMMAND, true, validator); + + if (isInvalidInput) { + logger.log(Level.WARNING, "Invalid parameter or value specified by user"); + logger.log(Level.INFO, "Unsuccessful deletion of prescription"); + return false; + } + return true; + } + + /** + * Check stock if stock exist. If stock exist, add the quantity to the stock quantity. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of medicines + * @param stockIdToPrescribe Stock ID prescribe. + * @param prescribedQuantity Quantity prescribed. + * @return Boolean value indicating if stock id exist. + */ + private boolean setStockQuantity(Ui ui, ArrayList medicines, int stockIdToPrescribe, + int prescribedQuantity) { + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Stock stock = (Stock) medicine; + + if (stock.getStockId() == stockIdToPrescribe) { + if (stock.isDeleted()) { + stock.setDeleted(false); + } + + int quantityToRestore = stock.getQuantity() + prescribedQuantity; + + if (quantityToRestore > stock.getMaxQuantity()) { + ui.print("Unable to delete prescription. Quantity added will exceed maximum quantity."); + ui.print("Maximum quantity: " + stock.getMaxQuantity() + " Total Quantity after deletion: " + + quantityToRestore); + return true; + } + stock.setQuantity(quantityToRestore); + } + } + return false; + } + +} + diff --git a/src/main/java/command/prescription/ListPrescriptionCommand.java b/src/main/java/command/prescription/ListPrescriptionCommand.java new file mode 100644 index 0000000000..5898e9b14c --- /dev/null +++ b/src/main/java/command/prescription/ListPrescriptionCommand.java @@ -0,0 +1,131 @@ +package command.prescription; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Prescription; +import utilities.comparators.PrescriptionComparator; +import inventory.Medicine; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.PrescriptionValidator; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +//@@author alvintan01 + +/** + * Helps to process the listprescription command together with filters and sort. + */ +public class ListPrescriptionCommand extends Command { + private static Logger logger = Logger.getLogger("ListPrescription"); + + public ListPrescriptionCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start listing of prescription records"); + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {}; + String[] optionalParameters = {CommandParameters.ID, CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.CUSTOMER_ID, CommandParameters.DATE, CommandParameters.STAFF, + CommandParameters.STOCK_ID, + CommandParameters.SORT, CommandParameters.REVERSED_SORT}; + + MedicineValidator validator = new PrescriptionValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.LIST_PRESCRIPTION_COMMAND, false, validator); + if (isInvalidInput) { + return; + } + + ArrayList filteredPrescriptions = new ArrayList<>(); + + assert (filteredPrescriptions != null) : "Array is not initialised"; + + for (Medicine medicine : medicines) { + if (medicine instanceof Prescription) { + filteredPrescriptions.add((Prescription) medicine); + } + } + filteredPrescriptions = filterPrescription(filteredPrescriptions); + + ui.printPrescriptions(filteredPrescriptions); + logger.log(Level.INFO, "Successful listing of prescription"); + } + + + /** + * Helps to filter prescription records based on the user's input. + * + * @param filteredPrescriptions Arraylist of Prescription objects. + * @return Arraylist of filtered Prescription objects based on the user's parameters values. + */ + private ArrayList filterPrescription(ArrayList filteredPrescriptions) { + for (String parameter : parameters.keySet()) { + String parameterValue = parameters.get(parameter); + switch (parameter) { + case CommandParameters.ID: + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((d) -> d.getPrescriptionId() == Integer.parseInt(parameterValue)) + .collect(Collectors.toList()); + break; + case CommandParameters.NAME: + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((d) -> d.getMedicineName().toUpperCase().contains(parameterValue.toUpperCase())) + .collect(Collectors.toList()); + break; + case CommandParameters.QUANTITY: + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((d) -> d.getQuantity() == Integer.parseInt(parameterValue)) + .collect(Collectors.toList()); + break; + case CommandParameters.CUSTOMER_ID: + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((d) -> d.getCustomerId().toUpperCase().contains(parameterValue.toUpperCase())) + .collect(Collectors.toList()); + break; + case CommandParameters.DATE: + try { + Date date = DateParser.stringToDate(parameterValue); + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((m) -> m.getDate().equals(date)) + .collect(Collectors.toList()); + } catch (ParseException e) { + e.printStackTrace(); + } + break; + case CommandParameters.STAFF: + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((d) -> d.getStaff().toUpperCase().contains(parameterValue.toUpperCase())) + .collect(Collectors.toList()); + break; + case CommandParameters.STOCK_ID: + filteredPrescriptions = (ArrayList) filteredPrescriptions.stream() + .filter((d) -> d.getStockId() == Integer.parseInt(parameterValue)) + .collect(Collectors.toList()); + break; + case CommandParameters.SORT: + filteredPrescriptions.sort(new PrescriptionComparator(parameterValue.toLowerCase(), false)); + break; + case CommandParameters.REVERSED_SORT: + filteredPrescriptions.sort(new PrescriptionComparator(parameterValue.toLowerCase(), true)); + break; + default: + return filteredPrescriptions; + } + } + return filteredPrescriptions; + } +} diff --git a/src/main/java/command/prescription/UpdatePrescriptionCommand.java b/src/main/java/command/prescription/UpdatePrescriptionCommand.java new file mode 100644 index 0000000000..9335cc3dca --- /dev/null +++ b/src/main/java/command/prescription/UpdatePrescriptionCommand.java @@ -0,0 +1,388 @@ +package command.prescription; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Prescription; +import inventory.Medicine; +import inventory.Stock; +import utilities.parser.MedicineValidator; +import utilities.parser.PrescriptionManager; +import utilities.parser.PrescriptionValidator; +import utilities.parser.StockManager; +import utilities.parser.StockValidator; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Logger; + +//@@author a-tph + +/** + * Update prescription information based on user input given prescription id. + */ +public class UpdatePrescriptionCommand extends Command { + private static Logger logger = Logger.getLogger("UpdatePrescription"); + + public UpdatePrescriptionCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {CommandParameters.ID}; + String[] optionalParameters = {CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.CUSTOMER_ID, CommandParameters.STAFF, CommandParameters.DATE}; + + MedicineValidator validator = new PrescriptionValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.UPDATE_PRESCRIPTION_COMMAND, true, validator); + if (isInvalidInput) { + return; + } + + Prescription prescription = PrescriptionManager.extractPrescriptionObject(parameters, medicines); + assert (prescription != null) : "Prescription object should not be null"; + + boolean hasNameParam = validator.hasNameParamChecker(parameters, prescription.getMedicineName()); + Date date = PrescriptionManager.getUpdatedDate(parameters, prescription.getDate()); + String customerId = PrescriptionManager.getUpdatedCustomerId(parameters, prescription.getCustomerId()); + String staffName = PrescriptionManager.getUpdatedStaff(parameters, prescription.getStaff()); + boolean hasQuantityParam = validator.hasQuantityParamChecker(parameters, prescription.getQuantity()); + + if (hasQuantityParam) { + boolean isZero = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)) == 0; + if (isZero) { + ui.print("Action aborted! Please use the delete command to remove the prescription."); + return; + } + } + boolean isSuccessfulUpdate = false; + + if (hasNameParam && hasQuantityParam) { + isSuccessfulUpdate = processGivenNameAndQuantity(ui, medicines, prescription, customerId, date, staffName); + } else if (hasNameParam && !hasQuantityParam) { + isSuccessfulUpdate = processGivenName(ui, medicines, prescription, customerId, date, staffName); + } else if (!hasNameParam && hasQuantityParam) { + isSuccessfulUpdate = processGivenQuantity(ui, medicines, prescription, customerId, date, staffName); + } else { + isSuccessfulUpdate = processOtherFields(ui, medicines, prescription, customerId, date, staffName); + } + + if (!isSuccessfulUpdate) { + return; + } + + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + } + + /** + * Processes name and quantity field provided by user for updating prescription information. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param prescription The associated prescription object. + * @param customerId CustomerId of customers. + * @param date Date of prescription. + * @param staffName Staff responsible for the prescription of medication. + * @return Boolean value true if update is successful. + */ + private boolean processGivenNameAndQuantity(Ui ui, ArrayList medicines, Prescription prescription, + String customerId, Date date, String staffName) { + StockValidator stockValidator = new StockValidator(); + String currentName = prescription.getMedicineName(); + int currentStockId = prescription.getStockId(); + String updatedName = parameters.get(CommandParameters.NAME); + + Stock targetRestoreStock = StockManager.extractStockObject(medicines, currentName, currentStockId); + if (targetRestoreStock == null) { + ui.print("Medicine not found in stock"); + return false; + } + + int restoreStockQuantity = targetRestoreStock.getQuantity(); + int restoredQuantity = prescription.getQuantity(); + int totalQuantity = restoredQuantity + restoreStockQuantity; + int restoreMaxQuantity = targetRestoreStock.getMaxQuantity(); + boolean isValidRestore = stockValidator.quantityValidityChecker(ui, totalQuantity, restoreMaxQuantity); + if (!isValidRestore) { + ui.print("Restoring of medication aborted!"); + return false; + } + + ArrayList targetPrescriptionStocks = StockManager.getUnexpiredFilteredStocksByName(medicines, + updatedName); + if (targetPrescriptionStocks.isEmpty()) { + ui.print("Action aborted! Either medication not found or stock has expired."); + return false; + } + + String updatedQuantity = parameters.get(CommandParameters.QUANTITY); + int prescriptionQuantity = Integer.parseInt(updatedQuantity); + int availableQuantity = StockManager.getTotalStockQuantity(medicines, updatedName); + boolean isValidPrescription = stockValidator.quantityValidityChecker(ui, prescriptionQuantity, + availableQuantity); + if (!isValidPrescription) { + ui.print("Prescription of medication aborted!"); + return false; + } + + // Guarantee is be able to restore & prescribe + PrescriptionManager.restoreStock(targetRestoreStock, totalQuantity); + ArrayList updatedPrescriptions = PrescriptionManager.prescribeStock(medicines, + targetPrescriptionStocks, prescriptionQuantity, customerId, date, staffName); + + // Add to prescription + for (Prescription updatedPrescription : updatedPrescriptions) { + medicines.add(updatedPrescription); + } + medicines.remove(prescription); + + ui.print("Restored " + restoredQuantity + " " + targetRestoreStock.getMedicineName()); + ui.print("Updated prescription information!"); + ui.printPrescriptions(updatedPrescriptions); + return true; + } + + /** + * Processes name field provided by user for updating prescription information. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param prescription The associated prescription object. + * @param customerId CustomerId of customers. + * @param date Date of prescription. + * @param staffName Staff responsible for the prescription of medication. + * @return Boolean value true if update is successful. + */ + private boolean processGivenName(Ui ui, ArrayList medicines, Prescription prescription, String customerId, + Date date, String staffName) { + StockValidator stockValidator = new StockValidator(); + String currentName = prescription.getMedicineName(); + int currentStockId = prescription.getStockId(); + String updatedName = parameters.get(CommandParameters.NAME); + Stock targetRestoreStock = StockManager.extractStockObject(medicines, currentName, currentStockId); + if (targetRestoreStock == null) { + ui.print("Medicine not found in stock"); + return false; + } + int restoreStockQuantity = targetRestoreStock.getQuantity(); + int restoredQuantity = prescription.getQuantity(); + int totalQuantity = restoredQuantity + restoreStockQuantity; + int restoreMaxQuantity = targetRestoreStock.getMaxQuantity(); + boolean isValidRestore = stockValidator.quantityValidityChecker(ui, totalQuantity, restoreMaxQuantity); + if (!isValidRestore) { + ui.print("Restoring of medication aborted!"); + return false; + } + + ArrayList targetPrescriptionStocks = StockManager.getUnexpiredFilteredStocksByName(medicines, + updatedName); + if (targetPrescriptionStocks.isEmpty()) { + ui.print("Action aborted! Either medication not found or stock has expired."); + return false; + } + int availableQuantity = StockManager.getTotalStockQuantity(medicines, updatedName); + boolean isValidPrescription = stockValidator.quantityValidityChecker(ui, restoredQuantity, availableQuantity); + if (!isValidPrescription) { + ui.print("Prescription of medication aborted!"); + return false; + } + + // Guarantee to be able to restore & prescribe + PrescriptionManager.restoreStock(targetRestoreStock, totalQuantity); + ArrayList updatedPrescriptions = PrescriptionManager.prescribeStock(medicines, + targetPrescriptionStocks, restoredQuantity, customerId, date, staffName); + + // Add to prescription + for (Prescription updatedPrescription : updatedPrescriptions) { + medicines.add(updatedPrescription); + } + medicines.remove(prescription); + ui.print("Restored " + restoredQuantity + " " + targetRestoreStock.getMedicineName()); + ui.print("Updated prescription information!"); + ui.printPrescriptions(updatedPrescriptions); + return true; + } + + /** + * Processes quantity field provided by user for updating prescription information. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param prescription The associated prescription object. + * @param customerId CustomerId of customers. + * @param date Date of prescription. + * @param staffName Staff responsible for the prescription of medication. + * @return Boolean value true if update is successful. + */ + private boolean processGivenQuantity(Ui ui, ArrayList medicines, Prescription prescription, + String customerId, Date date, String staffName) { + int currentQuantity = prescription.getQuantity(); + int updatedQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + boolean isSuccessful = false; + if (updatedQuantity < currentQuantity) { + isSuccessful = processRestoration(ui, medicines, prescription, customerId, date, staffName); + } else if (updatedQuantity > currentQuantity) { + isSuccessful = processPrescription(ui, medicines, prescription, customerId, date, staffName); + } + if (!isSuccessful) { + return false; + } + return true; + } + + /** + * Process prescription of medication for a prescription record. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param prescription The associated prescription object. + * @param customerId CustomerId of customers. + * @param date Date of prescription. + * @param staffName Staff responsible for the prescription of medication. + * @return Boolean value true if prescription is successful. + */ + private boolean processPrescription(Ui ui, ArrayList medicines, Prescription prescription, + String customerId, Date date, String staffName) { + StockValidator stockValidator = new StockValidator(); + String currentName = prescription.getMedicineName(); + int currentQuantity = prescription.getQuantity(); + int stockId = prescription.getStockId(); + int updatedQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + + ArrayList targetPrescriptionStocks = StockManager.getUnexpiredFilteredStocksByName(medicines, + currentName); + if (targetPrescriptionStocks.isEmpty()) { + ui.print("Action aborted! Either medication not found or stock has expired."); + return false; + } + + int prescribedQuantity = updatedQuantity - currentQuantity; + int stockQuantity = StockManager.getTotalStockQuantity(medicines, currentName); + boolean isValidPrescription = stockValidator.quantityValidityChecker(ui, prescribedQuantity, stockQuantity); + if (!isValidPrescription) { + ui.print("Prescription of medication aborted!"); + return false; + } + + // guarantee can prescribe + ArrayList updatedPrescriptions = PrescriptionManager.prescribeStock(medicines, + targetPrescriptionStocks, prescribedQuantity, customerId, date, staffName); + + medicines.remove(prescription); + for (Prescription updatedPrescription : updatedPrescriptions) { + if (updatedPrescription.getStockId() == stockId) { + int newQuantity = currentQuantity + updatedPrescription.getQuantity(); + updatedPrescription.setQuantity(newQuantity); + break; + } + } + + // Add to prescription + for (Prescription updatedPrescription : updatedPrescriptions) { + medicines.add(updatedPrescription); + } + ui.print("Updated prescription information!"); + ui.printPrescriptions(updatedPrescriptions); + return true; + } + + /** + * Process restoration for a prescription record. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param prescription The associated prescription object. + * @param customerId CustomerId of customers. + * @param date Date of prescription. + * @param staffName Staff responsible for the prescription of medication. + * @return Boolean value true if restoration is successful. + */ + private boolean processRestoration(Ui ui, ArrayList medicines, Prescription prescription, + String customerId, Date date, String staffName) { + StockValidator stockValidator = new StockValidator(); + String currentName = prescription.getMedicineName(); + int currentStockId = prescription.getStockId(); + int currentQuantity = prescription.getQuantity(); + int updatedQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + Stock stock = StockManager.extractStockObject(medicines, currentName, currentStockId); + if (stock == null) { + ui.print("Medicine not found in stock"); + return false; + } + + int restoreQuantity = currentQuantity - updatedQuantity; + int stockQuantity = stock.getQuantity(); + int stockMaxQuantity = stock.getMaxQuantity(); + int totalQuantity = stockQuantity + restoreQuantity; + boolean isValidRestore = stockValidator.quantityValidityChecker(ui, totalQuantity, stockMaxQuantity); + if (!isValidRestore) { + ui.print("Restoring of medication aborted!"); + return false; + } + + // guarantee can restore + int stockId = prescription.getStockId(); + PrescriptionManager.restoreStock(stock, totalQuantity); + + medicines.remove(prescription); + Prescription newPrescription = new Prescription(currentName, updatedQuantity, customerId, date, staffName, + stockId); + medicines.add(newPrescription); + + ArrayList updatedPrescriptions = new ArrayList<>(); + updatedPrescriptions.add(newPrescription); + ui.print("Restored " + restoreQuantity + " " + stock.getMedicineName()); + ui.print("Updated prescription information!"); + ui.printPrescriptions(updatedPrescriptions); + return true; + } + + /** + * Processes other fields provided by user for updating prescription information. + * The other field are the customerId, date and staffName. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param prescription The associated prescription object. + * @param customerId CustomerId of customers. + * @param date Date of prescription. + * @param staffName Staff responsible for the prescription of medication. + * @return Boolean value true if update is successful. + */ + private boolean processOtherFields(Ui ui, ArrayList medicines, Prescription prescription, + String customerId, Date date, String staffName) { + if (prescription == null) { + return false; + } + Prescription updatedPrescription = null; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Prescription)) { + continue; + } + Prescription targetPrescription = (Prescription) medicine; + boolean isSamePrescriptionId = targetPrescription.getPrescriptionId() == prescription.getPrescriptionId(); + if (isSamePrescriptionId) { + ((Prescription) medicine).setCustomerId(customerId); + ((Prescription) medicine).setDate(date); + ((Prescription) medicine).setStaff(staffName); + updatedPrescription = (Prescription) medicine; + break; + } + } + ArrayList updatedPrescriptions = new ArrayList<>(); + updatedPrescriptions.add(updatedPrescription); + ui.print("Updated prescription information!"); + ui.printPrescriptions(updatedPrescriptions); + return true; + } + +} diff --git a/src/main/java/command/stock/AddStockCommand.java b/src/main/java/command/stock/AddStockCommand.java new file mode 100644 index 0000000000..dbd601c0cb --- /dev/null +++ b/src/main/java/command/stock/AddStockCommand.java @@ -0,0 +1,297 @@ +package command.stock; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.StockManager; +import utilities.parser.StockValidator; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author deonchung + +/** + * Add medication based on user input. + * User input include name, price, quantity, expiry date, description and maximum quantity of medication. + */ +public class AddStockCommand extends Command { + private static Logger logger = Logger.getLogger("AddCommand"); + + public AddStockCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start addition of stock"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + StockValidator stockValidator = new StockValidator(); + ArrayList filteredStocks = new ArrayList<>(); + + String[] optionalParameters = {}; + String nameToAdd = parameters.get(CommandParameters.NAME); + boolean nameExist = false; + + if (parameters.containsKey(CommandParameters.NAME)) { + nameToAdd = parameters.get(CommandParameters.NAME); + for (Medicine medicine : medicines) { + if (medicine instanceof Stock && medicine.getMedicineName().equalsIgnoreCase(nameToAdd) + && !((Stock) medicine).isDeleted()) { + filteredStocks.add((Stock) medicine); + nameExist = true; + } + } + } + + if (nameExist) { + String[] requiredParameters = {CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.EXPIRY_DATE}; + + if (!checkValidParametersAndValues(ui, parameters, medicines, requiredParameters, + optionalParameters)) { + return; + } + + String expiryDate = parameters.get(CommandParameters.EXPIRY_DATE); + Date formatExpiryDate = checkExpiryDate(ui, expiryDate); + + if (formatExpiryDate == null) { //if medication has expired + return; + } + + String quantityToAdd = parameters.get(CommandParameters.QUANTITY); + + int totalStock = StockManager.getTotalStockQuantity(medicines, nameToAdd); + assert totalStock > 0 : "Total Stock should be more than 0"; + + if (isExpiryExist(ui, stockValidator, filteredStocks, quantityToAdd, formatExpiryDate, totalStock)) { + return; + } + + String[] requiredParams = {CommandParameters.NAME, CommandParameters.PRICE, + CommandParameters.QUANTITY, CommandParameters.EXPIRY_DATE}; + + if (!checkValidParametersAndValues(ui, parameters, medicines, requiredParams, + optionalParameters)) { + return; + } + + if (addSameMedicine(ui, medicines, nameToAdd, stockValidator, filteredStocks, quantityToAdd, + formatExpiryDate, totalStock)) { + return; + } + } else { + String[] requiredParameters = {CommandParameters.NAME, CommandParameters.PRICE, + CommandParameters.QUANTITY, CommandParameters.EXPIRY_DATE, + CommandParameters.DESCRIPTION, CommandParameters.MAX_QUANTITY}; + + if (!checkValidParametersAndValues(ui, parameters, medicines, requiredParameters, + optionalParameters)) { + return; + } + + String priceToAdd = parameters.get(CommandParameters.PRICE); + String quantityToAdd = parameters.get(CommandParameters.QUANTITY); + String descriptionToAdd = parameters.get(CommandParameters.DESCRIPTION); + String maxQuantityToAdd = parameters.get(CommandParameters.MAX_QUANTITY); + String expiryDate = parameters.get(CommandParameters.EXPIRY_DATE); + + Date formatExpiryDate = checkExpiryDate(ui, expiryDate); + if (formatExpiryDate == null) { //if medication has expired + return; + } + + int maxQuantity = Integer.parseInt(maxQuantityToAdd); + int quantity = Integer.parseInt(quantityToAdd); + double price = Double.parseDouble(priceToAdd); + + if (isValidQuantity(ui, stockValidator, maxQuantity, quantity)) { + return; + } + + addMedicine(ui, medicines, nameToAdd, descriptionToAdd, price, quantity, formatExpiryDate, maxQuantity); + } + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + } + + /** + * Check if medication had expired. + * + * @param ui Reference to the UI object to print messages. + * @param expiryDate Expiry Date of medication to add. + * @return Expiry Date of medication if medication has not expired. Null if medication has expired. + */ + private Date checkExpiryDate(Ui ui, String expiryDate) { + Date formatExpiryDate = null; + try { + formatExpiryDate = DateParser.stringToDate(expiryDate); + } catch (ParseException e) { + e.printStackTrace(); + } + + Date todayDate = new Date(); + String todayDateString = DateParser.dateToString(todayDate); + + Date formatTodayDate = null; + try { + formatTodayDate = DateParser.stringToDate(todayDateString); + } catch (ParseException e) { + e.printStackTrace(); + } + + if (formatExpiryDate.before(formatTodayDate)) { + ui.print("Unable to add medicine. Medicine has expired."); + return null; + } + return formatExpiryDate; + } + + /** + * Check if same expiry for the same medication name exist. + * + * @param ui Reference to the UI object to print messages. + * @param stockValidator Reference to StockValidator object. + * @param filteredStocks List of medication with the same medication name as user input. + * @param quantityToAdd Quantity of medication to add. + * @param formatExpiry Formatted Expiry Date of medication to add. + * @param totalStock Total Quantity of the same stock. + * @return Boolean Value indicating if same medication exists. + */ + private boolean isExpiryExist(Ui ui, StockValidator stockValidator, ArrayList filteredStocks, + String quantityToAdd, Date formatExpiry, int totalStock) { + for (Stock stock : filteredStocks) { + int quantity = Integer.parseInt(quantityToAdd); + + if (isValidQuantity(ui, stockValidator, stock.getMaxQuantity(), totalStock + quantity)) { + return true; + } + + if (formatExpiry.equals(stock.getExpiry())) { + quantity += stock.getQuantity(); + stock.setQuantity(quantity); + ui.print("Same Medication and Expiry Date exist. Using existing price," + + " description and maximum quantity. Updating existing quantity."); + ui.printStock(stock); + return true; + } + } + return false; + } + + /** + * Adds the medication to same stock if same name exist. + * + * @param ui Reference to the UI object to print messages. + * @param medicines List of all medicines. + * @param nameToAdd Name of medication to add. + * @param stockValidator Reference to StockValidator object. + * @param filteredStocks List of medication with the same medication name as user input. + * @param quantityToAdd Quantity of medication to add. + * @param formatExpiry Formatted Expiry Date of medication to add. + * @param totalStock Total Quantity of the same stock. + * @return Boolean Value indicating if the same medication name exist. + */ + private boolean addSameMedicine(Ui ui, ArrayList medicines, String nameToAdd, + StockValidator stockValidator, + ArrayList filteredStocks, String quantityToAdd, Date formatExpiry, + int totalStock) { + for (Stock stock : filteredStocks) { + + int quantity = Integer.parseInt(quantityToAdd); + int existingMaxQuantity = stock.getMaxQuantity(); + + if (isValidQuantity(ui, stockValidator, existingMaxQuantity, totalStock + quantity)) { + return true; + } + + String existingDescription = stock.getDescription(); + String priceToAdd = parameters.get(CommandParameters.PRICE); + double price = Double.parseDouble(priceToAdd); + ui.print("Medicine exists. Using existing description and maximum quantity."); + addMedicine(ui, medicines, nameToAdd, existingDescription, price, + quantity, formatExpiry, existingMaxQuantity); + return true; + } + return false; + } + + /** + * Check if quantity added is less than the maximum quantity of stock. + * + * @param ui Reference to the UI object to print messages. + * @param stockValidator Reference to StockValidator object. + * @param maxQuantity Maximum quantity of medication. + * @param quantity Quantity of medication to add. + * @return Boolean Value indicating if quantity added is less than maximum quantity. + */ + private boolean isValidQuantity(Ui ui, StockValidator stockValidator, int maxQuantity, int quantity) { + boolean isValidQuantity = stockValidator.quantityValidityChecker(ui, quantity, maxQuantity); + + if (!isValidQuantity) { + logger.log(Level.WARNING, "Invalid Quantity is specified by user"); + logger.log(Level.INFO, "Unsuccessful addition of stock"); + return true; + } + return false; + } + + /** + * Check if input contains Invalid Parameters and Invalid Parameter Values. + * + * @param ui Reference to the UI object to print messages. + * @param parameters The parameter that is not found. + * @param medicines List of all medicines. + * @param requiredParameters The required parameters to check. + * @param optionalParameters The optional parameters to check. + * @return Boolean value indicating if parameter and parameter values are valid. + */ + private boolean checkValidParametersAndValues(Ui ui, LinkedHashMap parameters, + ArrayList medicines, String[] requiredParameters, + String[] optionalParameters) { + MedicineValidator validator = new StockValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.ADD_STOCK_COMMAND, false, validator); + if (isInvalidInput) { + logger.log(Level.INFO, "Unsuccessful addition of stock"); + return false; + } + return true; + } + + /** + * Add medication based on user input. + * + * @param ui Reference to the UI object to print messages. + * @param medicines List of all medicines. + * @param name Name of medication to add. + * @param description Description of medication to add. + * @param price Price of medication to add. + * @param quantity Quantity of medication to add. + * @param expiryDate Expiry Date of medication to add. + * @param maxQuantity Maximum Quantity of medication to add. + */ + private void addMedicine(Ui ui, ArrayList medicines, String name, String description, + double price, int quantity, Date expiryDate, int maxQuantity) { + + Stock stock = new Stock(name, price, quantity, expiryDate, description, maxQuantity); + medicines.add(stock); + ui.print("Medication added: " + name); + ui.printStock(stock); + logger.log(Level.INFO, "Successful addition of stock"); + } + +} diff --git a/src/main/java/command/stock/DeleteStockCommand.java b/src/main/java/command/stock/DeleteStockCommand.java new file mode 100644 index 0000000000..5876e22e39 --- /dev/null +++ b/src/main/java/command/stock/DeleteStockCommand.java @@ -0,0 +1,145 @@ +package command.stock; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.StockValidator; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author RemusTeo + +/** + * Delete medication based on user input given stock id. + */ +public class DeleteStockCommand extends Command { + private static Logger logger = Logger.getLogger("DeleteStock"); + + public DeleteStockCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start deletion of stock"); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + + String[] requiredParameters = {}; + String[] optionalParameters = {CommandParameters.ID, CommandParameters.EXPIRING}; + + MedicineValidator validator = new StockValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.DELETE_STOCK_COMMAND, true, validator); + if (isInvalidInput) { + logger.log(Level.INFO, "Unsuccessful deletion of stock"); + return; + } + + boolean hasStockId = parameters.containsKey(CommandParameters.ID); + boolean hasExpiryDate = parameters.containsKey(CommandParameters.EXPIRING); + + // Both fields should not be provided for deletion of stock. + if (hasStockId && hasExpiryDate) { + ui.print("Deleted aborted! Please provide only one parameter"); + return; + } + + if (hasStockId && !hasExpiryDate) { + deleteStockById(ui, medicines); + } else if (!hasStockId && hasExpiryDate) { + deleteStockByExpiry(ui, medicines); + } + + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + logger.log(Level.INFO, "Successful deletion of stock"); + } + + /** + * Deletion of stock given an id. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + */ + private void deleteStockById(Ui ui, ArrayList medicines) { + String stockIdToDelete = parameters.get(CommandParameters.ID); + int stockId = Integer.parseInt(stockIdToDelete); + + assert stockId > 0 : "Stock Id should be more than 0"; + assert stockId <= Stock.getStockCount() : "Stock Id should not exceed max stock count"; + + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Stock stock = (Stock) medicine; + if (stock.getStockId() == stockId) { + stock.setQuantity(0); + stock.setDeleted(true); + logger.log(Level.INFO, "Stock id found and deleted"); + break; + } + } + ui.print("Deleted row with Stock Id: " + stockId); + } + + //@@author a-tph + + /** + * Deletion of expired stocks given a date. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + */ + private void deleteStockByExpiry(Ui ui, ArrayList medicines) { + String dateString = parameters.get(CommandParameters.EXPIRING); + Date date = null; + try { + date = DateParser.stringToDate(dateString); + } catch (ParseException e) { + e.printStackTrace(); + } + + + int rowsDeleted = 0; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + + Stock stock = (Stock) medicine; + Date stockExpiryDate = stock.getExpiry(); + if (stock.isDeleted()) { + continue; + } + + if (stockExpiryDate.before(date) || stockExpiryDate.equals(date)) { + stock.setQuantity(0); + stock.setDeleted(true); + rowsDeleted++; + } + } + + assert rowsDeleted >= 0 : "Rows deleted cannot be negative"; + if (rowsDeleted > 0) { + logger.log(Level.INFO, "Expired stock found: deleted " + rowsDeleted); + ui.print("Deleted expired medications! Rows deleted: " + rowsDeleted); + } else { + logger.log(Level.INFO, "No expired stocks found"); + ui.print("Delete aborted! Unable to find expired medications!"); + } + } +} diff --git a/src/main/java/command/stock/ListStockCommand.java b/src/main/java/command/stock/ListStockCommand.java new file mode 100644 index 0000000000..a7dca43275 --- /dev/null +++ b/src/main/java/command/stock/ListStockCommand.java @@ -0,0 +1,163 @@ +package command.stock; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Stock; +import utilities.comparators.StockComparator; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.StockManager; +import utilities.parser.StockValidator; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +//@@author jiangweichen835 + +/** + * Helps to process the liststock command together with filters and sort. + */ +public class ListStockCommand extends Command { + private static Logger logger = Logger.getLogger("ListStock"); + + public ListStockCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start listing of available stock"); + + Ui ui = Ui.getInstance(); + + String[] requiredParameter = {}; + String[] optionalParameters = {CommandParameters.ID, CommandParameters.NAME, CommandParameters.PRICE, + CommandParameters.QUANTITY, CommandParameters.EXPIRY_DATE, CommandParameters.DESCRIPTION, + CommandParameters.MAX_QUANTITY, CommandParameters.SORT, CommandParameters.REVERSED_SORT, + CommandParameters.EXPIRING, CommandParameters.LOW}; + + ArrayList medicines = Medicine.getInstance(); + if (!checkValidParameterValues(ui, medicines, requiredParameter, optionalParameters)) { + return; + } + + ArrayList filteredStocks = new ArrayList<>(); + + assert (filteredStocks != null) : "Array is not initialised"; + + for (Medicine medicine : medicines) { + if (medicine instanceof Stock) { // Ensure that it is a medicine object + Stock stock = (Stock) medicine; + if (!stock.isDeleted()) { + filteredStocks.add(stock); + } + } + } + filteredStocks = filterStocks(filteredStocks, medicines); + ui.printStocks(filteredStocks, medicines); + logger.log(Level.INFO, "Successful listing of stock"); + } + + /** + * Helps to filter stocks based on the user's input. + * + * @param filteredStocks Arraylist of Stock objects. + * @param medicines Arraylist of Medicines objects. + * @return Arraylist of filtered Stock objects based on the user's parameters values. + */ + private ArrayList filterStocks(ArrayList filteredStocks, ArrayList medicines) { + for (String parameter : parameters.keySet()) { + String parameterValue = parameters.get(parameter); + switch (parameter) { + case CommandParameters.ID: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + m.getStockId() == Integer.parseInt(parameterValue)).collect(Collectors.toList()); + break; + case CommandParameters.NAME: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + (m.getMedicineName().toUpperCase()).contains(parameterValue.toUpperCase())) + .collect(Collectors.toList()); + break; + case CommandParameters.PRICE: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + m.getPrice() == Double.parseDouble(parameterValue)).collect(Collectors.toList()); + break; + case CommandParameters.LOW: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + StockManager.getTotalStockQuantity(medicines, m.getMedicineName()) + <= Integer.parseInt(parameterValue)).collect(Collectors.toList()); + break; + case CommandParameters.QUANTITY: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + m.getQuantity() == Integer.parseInt(parameterValue)).collect(Collectors.toList()); + break; + case CommandParameters.EXPIRING: + try { + Date expiryDate = DateParser.stringToDate(parameterValue); + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + m.getExpiry().before(expiryDate) || m.getExpiry().equals(expiryDate)) + .collect(Collectors.toList()); + } catch (ParseException e) { + e.printStackTrace(); + } + break; + case CommandParameters.EXPIRY_DATE: + try { + Date expiryDate = DateParser.stringToDate(parameterValue); + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + m.getExpiry().equals(expiryDate)).collect(Collectors.toList()); + } catch (ParseException e) { + e.printStackTrace(); + } + break; + case CommandParameters.DESCRIPTION: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + (m.getDescription().toUpperCase()).contains(parameterValue.toUpperCase())) + .collect(Collectors.toList()); + break; + case CommandParameters.MAX_QUANTITY: + filteredStocks = (ArrayList) filteredStocks.stream().filter((m) -> + m.getMaxQuantity() == Integer.parseInt(parameterValue)).collect(Collectors.toList()); + break; + case CommandParameters.SORT: + filteredStocks.sort(new StockComparator(parameterValue.toLowerCase(), false)); + break; + case CommandParameters.REVERSED_SORT: + filteredStocks.sort(new StockComparator(parameterValue.toLowerCase(), true)); + break; + default: + return filteredStocks; + } + } + return filteredStocks; + } + + /** + * Checks if user input are valid. + * + * @param ui Reference to the UI object to print messages. + * @param medicines List of all medicines. + * @param requiredParameters The required parameters to check. + * @param optionalParameters The optional parameters to check. + * @return Boolean value indicating if parameter and parameter values are valid. + */ + private boolean checkValidParameterValues(Ui ui, ArrayList medicines, String[] requiredParameters, + String[] optionalParameters) { + MedicineValidator validator = new StockValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.LIST_STOCK_COMMAND, false, validator); + if (isInvalidInput) { + return false; + } + + return true; + } +} diff --git a/src/main/java/command/stock/UpdateStockCommand.java b/src/main/java/command/stock/UpdateStockCommand.java new file mode 100644 index 0000000000..624609a667 --- /dev/null +++ b/src/main/java/command/stock/UpdateStockCommand.java @@ -0,0 +1,300 @@ +package command.stock; + +import command.Command; +import command.CommandParameters; +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.parser.MedicineValidator; +import utilities.parser.StockManager; +import utilities.parser.StockValidator; +import utilities.storage.Storage; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author a-tph + +/** + * Update medication information based on user input given stock id. + */ +public class UpdateStockCommand extends Command { + private static Logger logger = Logger.getLogger("UpdateStock"); + + public UpdateStockCommand(LinkedHashMap parameters) { + this.parameters = parameters; + } + + @Override + public void execute() { + logger.log(Level.INFO, "Start of UpdateStock command execution."); + + Ui ui = Ui.getInstance(); + ArrayList medicines = Medicine.getInstance(); + String[] requiredParameters = {CommandParameters.ID}; + String[] optionalParameters = {CommandParameters.PRICE, CommandParameters.QUANTITY, + CommandParameters.EXPIRY_DATE, CommandParameters.DESCRIPTION, CommandParameters.NAME, + CommandParameters.MAX_QUANTITY}; + + MedicineValidator validator = new StockValidator(); + boolean isInvalidInput = validator.containsInvalidParametersAndValues(ui, medicines, parameters, + requiredParameters, optionalParameters, CommandSyntax.UPDATE_STOCK_COMMAND, true, validator); + if (isInvalidInput) { + return; + } + + Stock stock = StockManager.extractStockObject(parameters, medicines); + boolean isValidQuantityValues = processQuantityValues(ui, medicines, stock); + if (!isValidQuantityValues) { + return; + } + boolean isValidExpDate = processDateInput(ui, medicines, stock); + if (!isValidExpDate) { + return; + } + + ArrayList oldFilteredStocks = StockManager.getFilteredStocksByName(medicines, stock.getMedicineName()); + if (parameters.containsKey(CommandParameters.NAME)) { + addNewRowForUpdates(oldFilteredStocks, medicines); + stock = getNewStock(medicines, stock); + } + ArrayList filteredStocks = StockManager.getFilteredStocksByName(medicines, stock.getMedicineName()); + + // Default value for updating all affected rows + boolean isAffectedCommand = checkAffectedCommand(); + if (!isAffectedCommand) { + filteredStocks.clear(); + filteredStocks.add(stock); + } + + int rowsAffected = filteredStocks.size(); + setUpdatesByStockId(filteredStocks, stock); + ui.print("Updated! Number of rows affected: " + rowsAffected); + printUpdatedStockId(ui, filteredStocks, oldFilteredStocks); + + ui.printStocks(filteredStocks, medicines); + Storage storage = Storage.getInstance(); + storage.saveData(medicines); + logger.log(Level.INFO, "End of UpdateStock command execution."); + } + + /** + * Prints the change of stock id for affected records. + * + * @param ui Reference to the UI object passed by Main to print messages. + * @param filteredStocks Filtered stocks of the updated records. + * @param oldFilteredStocks Filtered stocks of the old records. + */ + private void printUpdatedStockId(Ui ui, ArrayList filteredStocks, ArrayList oldFilteredStocks) { + if (parameters.containsKey(CommandParameters.NAME)) { + ui.print("Stock Id changed from:"); + for (int i = 0; i < filteredStocks.size(); i++) { + ui.print(oldFilteredStocks.get(i).getStockId() + " -> " + filteredStocks.get(i).getStockId()); + } + } + } + + /** + * Checks if the command triggers multiple row updates. + * + * @return Boolean true if the command triggers multiple row updates. + */ + private boolean checkAffectedCommand() { + String[] affectedCommands = {CommandParameters.NAME, CommandParameters.DESCRIPTION, + CommandParameters.MAX_QUANTITY}; + boolean isAffectedCommand = false; + for (String affectedCommand : affectedCommands) { + if (parameters.containsKey(affectedCommand)) { + isAffectedCommand = true; + break; + } + } + return isAffectedCommand; + } + + /** + * Retrieves the updated stock object. + * + * @param medicines Arraylist of all medicines. + * @param stock Stock object of the given stock id. + * @return The updated stock object. + */ + private Stock getNewStock(ArrayList medicines, Stock stock) { + Stock newStock = null; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + if (((Stock) medicine).isDeleted()) { + continue; + } + boolean isSameName = medicine.getMedicineName().equalsIgnoreCase(stock.getMedicineName()); + boolean isSameExpDate = ((Stock) medicine).getExpiry().equals(stock.getExpiry()); + if (isSameName && isSameExpDate) { + String newStockId = String.valueOf(((Stock) medicine).getStockId()); + parameters.put(CommandParameters.ID, newStockId); + newStock = (Stock) medicine; + break; + } + } + return newStock; + } + + /** + * Add new rows when medicine name gets updated. + * + * @param filteredStocks Arraylist of filtered medicine stocks. + * @param medicines Arraylist of all medicines. + */ + private void addNewRowForUpdates(ArrayList filteredStocks, ArrayList medicines) { + // Initialise new stock to get a new Stock Id + for (Stock stock : filteredStocks) { + String name = stock.getMedicineName(); + double price = stock.getPrice(); + int quantity = stock.getQuantity(); + Date expiryDate = stock.getExpiry(); + String description = stock.getDescription(); + int maxQuantity = stock.getMaxQuantity(); + Stock newStock = new Stock(name, price, quantity, expiryDate, description, maxQuantity); + medicines.add(newStock); + } + + for (Stock stock : filteredStocks) { + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + int stockId = ((Stock) medicine).getStockId(); + if (stockId == stock.getStockId()) { + ((Stock) medicine).setDeleted(true); + medicine.setQuantity(0); + } + } + } + } + + /** + * Process valid date input to be updated given a stock id. + * + * @param ui Reference to the UI object passed by Main to print messages. + * @param medicines Arraylist of all medicines. + * @param stock Stock object of the given stock id. + * @return Boolean value indicating if quantity values are valid. + */ + private boolean processDateInput(Ui ui, ArrayList medicines, Stock stock) { + logger.log(Level.INFO, "Processing date input for update stock..."); + boolean hasExpiryDate = parameters.containsKey(CommandParameters.EXPIRY_DATE); + if (!hasExpiryDate) { + return true; + } + + Date expiryDate = null; + try { + expiryDate = DateParser.stringToDate(parameters.get(CommandParameters.EXPIRY_DATE)); + } catch (ParseException e) { + e.printStackTrace(); + } + StockValidator stockValidator = new StockValidator(); + String name = stock.getMedicineName(); + logger.log(Level.INFO, "End processing date input for update stock."); + return stockValidator.dateValidityChecker(ui, medicines, expiryDate, name); + } + + /** + * Process quantity values to be updated given a stock id. + * + * @param ui Reference to the UI object passed by Main to print messages. + * @param medicines Arraylist of all medicines. + * @param stock Stock object of the given stock id. + * @return Boolean value indicating if quantity values are valid. + */ + private boolean processQuantityValues(Ui ui, ArrayList medicines, Stock stock) { + logger.log(Level.INFO, "Processing quantity values for update stock..."); + String name = stock.getMedicineName(); + int quantity = 0; + int maxQuantity = 0; + int totalStockQuantity = 0; + int initialQuantity = 0; + int updatedQuantity = 0; + + boolean hasQuantity = parameters.containsKey(CommandParameters.QUANTITY); + boolean hasMaxQuantity = parameters.containsKey(CommandParameters.MAX_QUANTITY); + + // initialise quantity and max quantity based on the different combinations of user inputs + if (hasQuantity && hasMaxQuantity) { + totalStockQuantity = StockManager.getTotalStockQuantity(medicines, name); + initialQuantity = stock.getQuantity(); + updatedQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + quantity = totalStockQuantity - initialQuantity + updatedQuantity; + maxQuantity = Integer.parseInt(parameters.get(CommandParameters.MAX_QUANTITY)); + } + + if (hasQuantity && !hasMaxQuantity) { + totalStockQuantity = StockManager.getTotalStockQuantity(medicines, name); + initialQuantity = stock.getQuantity(); + updatedQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + quantity = totalStockQuantity - initialQuantity + updatedQuantity; + maxQuantity = StockManager.getMaxStockQuantity(medicines, name); + } + + if (!hasQuantity && hasMaxQuantity) { + quantity = StockManager.getTotalStockQuantity(medicines, name); + maxQuantity = Integer.parseInt(parameters.get(CommandParameters.MAX_QUANTITY)); + } + StockValidator stockValidator = new StockValidator(); + logger.log(Level.INFO, "End processing quantity values for update stock."); + return stockValidator.quantityValidityChecker(ui, quantity, maxQuantity); + } + + /** + * Update values provided by user for a given stock id. + * + * @param filteredStocks Arraylist of filtered medicine stocks. + * @param stock Stock object of the given stock id. + */ + private void setUpdatesByStockId(ArrayList filteredStocks, Stock stock) { + logger.log(Level.INFO, "Attempt to update stock information."); + for (String parameter : parameters.keySet()) { + String parameterValue = parameters.get(parameter); + switch (parameter) { + case CommandParameters.NAME: + for (Stock targetStock : filteredStocks) { + targetStock.setMedicineName(parameterValue); + } + break; + case CommandParameters.PRICE: + stock.setPrice(Double.parseDouble(parameterValue)); + break; + case CommandParameters.QUANTITY: + stock.setQuantity(Integer.parseInt(parameterValue)); + break; + case CommandParameters.EXPIRY_DATE: + try { + stock.setExpiry(DateParser.stringToDate(parameterValue)); + } catch (ParseException e) { + e.printStackTrace(); + } + break; + case CommandParameters.DESCRIPTION: + for (Stock targetStock : filteredStocks) { + targetStock.setDescription(parameterValue); + } + break; + case CommandParameters.MAX_QUANTITY: + for (Stock targetStock : filteredStocks) { + targetStock.setMaxQuantity(Integer.parseInt(parameterValue)); + } + break; + default: + break; + } + } + logger.log(Level.INFO, "Updated stock information with given user input"); + } +} diff --git a/src/main/java/errors/InvalidCommandException.java b/src/main/java/errors/InvalidCommandException.java new file mode 100644 index 0000000000..148585d64e --- /dev/null +++ b/src/main/java/errors/InvalidCommandException.java @@ -0,0 +1,9 @@ +package errors; + +//@@author alvintan01 +/** + * Represents the InvalidCommandException thrown when a command is not found in command.CommandList. + */ + +public class InvalidCommandException extends Exception { +} diff --git a/src/main/java/errors/InvalidDataException.java b/src/main/java/errors/InvalidDataException.java new file mode 100644 index 0000000000..2635587996 --- /dev/null +++ b/src/main/java/errors/InvalidDataException.java @@ -0,0 +1,11 @@ +package errors; + +//@@author RemusTeo +/** + * Represents the InvalidDataException thrown when data is corrupted or missing during loading from file. + */ +public class InvalidDataException extends Exception { + public InvalidDataException(String message) { + super(message); + } +} diff --git a/src/main/java/inventory/Medicine.java b/src/main/java/inventory/Medicine.java new file mode 100644 index 0000000000..d753160ef1 --- /dev/null +++ b/src/main/java/inventory/Medicine.java @@ -0,0 +1,50 @@ +package inventory; + +import java.util.ArrayList; + +/** + * Represents the generic stock for the application. It contains the medicine name and quantity. + * It is inherited by Prescription, Medicine and Order objects. + */ +public abstract class Medicine { + protected String medicineName; + protected int quantity; + private static ArrayList medicines = null; + + /** + * Helps to create the medicine arraylist or returns the arraylist if it exists. + * + * @return The medicine's arraylist. + */ + public static ArrayList getInstance() { + if (medicines == null) { + medicines = new ArrayList<>(); + } + return medicines; + } + + public Medicine(String medicineName, int quantity) { + this.medicineName = medicineName; + this.quantity = quantity; + } + + public String getMedicineName() { + return medicineName; + } + + public void setMedicineName(String medicineName) { + this.medicineName = medicineName; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public abstract String toFileFormat(); + + public abstract String toArchiveFormat(); +} diff --git a/src/main/java/inventory/Order.java b/src/main/java/inventory/Order.java new file mode 100644 index 0000000000..8d6f391783 --- /dev/null +++ b/src/main/java/inventory/Order.java @@ -0,0 +1,91 @@ +package inventory; + +import utilities.parser.DateParser; + +import java.util.Date; + +/** + * Represents an Order. An Order is represented by order_id, medicine name, quantity, date and isDelivered. + */ +public class Order extends Medicine { + public static final String ID = "ID"; + public static final String NAME = "NAME"; + public static final String QUANTITY = "QUANTITY"; + public static final String DATE = "DATE"; + public static final String STATUS = "STATUS"; + + // Used for sorting + public static final String ID_LOWERCASE = "id"; + public static final String NAME_LOWERCASE = "name"; + public static final String QUANTITY_LOWERCASE = "quantity"; + public static final String DATE_LOWERCASE = "date"; + public static final String STATUS_LOWERCASE = "status"; + + public static final String[] COLUMNS = {ID, NAME, QUANTITY, DATE, STATUS}; + + private static int orderCount = 0; + protected int orderId; + protected Date date; + protected boolean isDelivered; + + public Order(String medicineName, int quantity, Date date) { + super(medicineName, quantity); + orderCount++; + this.orderId = orderCount; + this.date = date; + this.isDelivered = false; + } + + public static int getOrderCount() { + return orderCount; + } + + public static void setOrderCount(int orderCount) { + Order.orderCount = orderCount; + } + + public int getOrderId() { + return orderId; + } + + public void setOrderId(int orderId) { + this.orderId = orderId; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public boolean isDelivered() { + return isDelivered; + } + + public void setDelivered() { + isDelivered = true; + } + + public String getStatus() { + if (isDelivered) { + return "DELIVERED"; + } else { + return "PENDING"; + } + } + + public String toFileFormat() { + String fileFormat = getOrderId() + "|" + getMedicineName().toUpperCase() + "|" + getQuantity() + "|" + + DateParser.dateToString(getDate()) + "|" + getStatus(); + return fileFormat; + } + + public String toArchiveFormat() { + String archiveFormat = "[ORDER ID: " + getOrderId() + "] " + getQuantity() + " " + + getMedicineName().toUpperCase() + " WAS ORDERED ON " + DateParser.dateToString(getDate()) + + ". STATUS: " + getStatus(); + return archiveFormat; + } +} diff --git a/src/main/java/inventory/Prescription.java b/src/main/java/inventory/Prescription.java new file mode 100644 index 0000000000..a76724c1bc --- /dev/null +++ b/src/main/java/inventory/Prescription.java @@ -0,0 +1,111 @@ +package inventory; + +import utilities.parser.DateParser; + +import java.util.Date; + +/** + * Represents a Dispensed object. A Dispensed object is represented by medicine name, quantity, customer's NRIC, + * date and staff name. + */ +public class Prescription extends Medicine { + public static final String ID = "ID"; + public static final String NAME = "NAME"; + public static final String QUANTITY = "QUANTITY"; + public static final String CUSTOMER_ID = "CUSTOMER_ID"; + public static final String DATE = "DATE"; + public static final String STAFF = "STAFF"; + public static final String STOCK_ID = "STOCK_ID"; + + // Used for sorting + public static final String ID_LOWERCASE = "id"; + public static final String NAME_LOWERCASE = "name"; + public static final String QUANTITY_LOWERCASE = "quantity"; + public static final String CUSTOMER_ID_LOWERCASE = "customer_id"; + public static final String DATE_LOWERCASE = "date"; + public static final String STAFF_LOWERCASE = "staff"; + public static final String STOCK_ID_LOWERCASE = "stock_id"; + + public static final String[] COLUMNS = {ID, NAME, QUANTITY, CUSTOMER_ID, DATE, STAFF, STOCK_ID}; + + private static int prescriptionCount = 0; + protected int prescriptionId; + protected String customerId; + protected Date date; + protected String staff; + protected int stockId; + + public Prescription(String medicineName, int quantity, String customerId, Date date, String staff, int stockId) { + super(medicineName, quantity); + prescriptionCount++; + this.prescriptionId = prescriptionCount; + this.customerId = customerId; + this.date = date; + this.staff = staff; + this.stockId = stockId; + } + + public static int getPrescriptionCount() { + return prescriptionCount; + } + + public static void setPrescriptionCount(int prescriptionCount) { + Prescription.prescriptionCount = prescriptionCount; + } + + public int getPrescriptionId() { + return prescriptionId; + } + + public void setPrescriptionId(int prescriptionId) { + this.prescriptionId = prescriptionId; + } + + public String getCustomerId() { + return customerId; + } + + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public int getStockId() { + return stockId; + } + + public void setStockId(int stockId) { + this.stockId = stockId; + } + + public String toFileFormat() { + String fileFormat = getPrescriptionId() + "|" + getMedicineName().toUpperCase() + "|" + getQuantity() + "|" + + getCustomerId().toUpperCase() + "|" + DateParser.dateToString(getDate()) + "|" + + getStaff().toUpperCase() + "|" + getStockId(); + return fileFormat; + } + + public String toArchiveFormat() { + String archiveFormat = "[PRESCRIPTION ID: " + getPrescriptionId() + "] " + getQuantity() + " " + + getMedicineName().toUpperCase() + " [STOCK ID: " + getStockId() + "] WAS PRESCRIBED BY " + + getStaff().toUpperCase() + " TO " + getCustomerId().toUpperCase() + " ON " + + DateParser.dateToString(getDate()); + return archiveFormat; + } + +} diff --git a/src/main/java/inventory/Stock.java b/src/main/java/inventory/Stock.java new file mode 100644 index 0000000000..51de2aa156 --- /dev/null +++ b/src/main/java/inventory/Stock.java @@ -0,0 +1,117 @@ +package inventory; + +import utilities.parser.DateParser; + +import java.util.Date; + +/** + * Represents a Medicine object. A Medicine object is represented by stock_id, name, price, quantity, expiry, + * description and max quantity. + */ +public class Stock extends Medicine { + public static final String ID = "ID"; + public static final String NAME = "NAME"; + public static final String PRICE = "PRICE"; + public static final String QUANTITY = "QUANTITY"; + public static final String EXPIRY_DATE = "EXPIRY_DATE"; + public static final String DESCRIPTION = "DESCRIPTION"; + public static final String MAX_QUANTITY = "MAX_QUANTITY"; + + // Used for sorting + public static final String ID_LOWERCASE = "id"; + public static final String NAME_LOWERCASE = "name"; + public static final String PRICE_LOWERCASE = "price"; + public static final String QUANTITY_LOWERCASE = "quantity"; + public static final String EXPIRY_DATE_LOWERCASE = "expiry"; + public static final String DESCRIPTION_LOWERCASE = "description"; + public static final String MAX_QUANTITY_LOWERCASE = "max_quantity"; + + public static final String[] COLUMNS = {ID, NAME, PRICE, QUANTITY, EXPIRY_DATE, DESCRIPTION, MAX_QUANTITY}; + + private static int stockCount = 0; + protected int stockId; + protected double price; + protected Date expiry; + protected String description; + protected int maxQuantity; + protected boolean isDeleted = false; + + public Stock(String name, double price, int quantity, Date expiry, String description, int maxQuantity) { + super(name, quantity); + stockCount++; + this.stockId = stockCount; + this.price = price; + this.expiry = expiry; + this.description = description; + this.maxQuantity = maxQuantity; + } + + public static int getStockCount() { + return stockCount; + } + + public static void setStockCount(int stockCount) { + Stock.stockCount = stockCount; + } + + public int getStockId() { + return stockId; + } + + public void setStockId(int stockId) { + this.stockId = stockId; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public Date getExpiry() { + return expiry; + } + + public void setExpiry(Date expiry) { + this.expiry = expiry; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getMaxQuantity() { + return maxQuantity; + } + + public void setMaxQuantity(int maxQuantity) { + this.maxQuantity = maxQuantity; + } + + public boolean isDeleted() { + return isDeleted; + } + + public void setDeleted(boolean deleted) { + isDeleted = deleted; + } + + public String toFileFormat() { + String fileFormat = getStockId() + "|" + getMedicineName().toUpperCase() + "|" + getPrice() + "|" + + getQuantity() + "|" + DateParser.dateToString(getExpiry()) + "|" + getDescription().toUpperCase() + + "|" + getMaxQuantity() + "|" + isDeleted(); + return fileFormat; + } + + // Dummy method since Stock does not use archive. + public String toArchiveFormat() { + String archiveFormat = ""; + return archiveFormat; + } +} diff --git a/src/main/java/utilities/comparators/OrderComparator.java b/src/main/java/utilities/comparators/OrderComparator.java new file mode 100644 index 0000000000..da53a9a5a0 --- /dev/null +++ b/src/main/java/utilities/comparators/OrderComparator.java @@ -0,0 +1,52 @@ +package utilities.comparators; + +import command.CommandParameters; +import inventory.Order; + +import java.util.Comparator; + +//@@author alvintan01 + +/** + * Helps to sort the order based on the column provided. + */ +public class OrderComparator implements Comparator { + private String column; + private Boolean isReversed; + + public OrderComparator(String column, Boolean isReversed) { + this.column = column; + this.isReversed = isReversed; + } + + @Override + public int compare(Order order1, Order order2) { + Order order; + + if (isReversed) { // If the user chooses to sort in reverse order + order = order2; + order2 = order1; + order1 = order; + } + + switch (column) { + case Order.ID_LOWERCASE: + case CommandParameters.ID: + return Integer.compare(order1.getOrderId(), order2.getOrderId()); + case Order.NAME_LOWERCASE: + case CommandParameters.NAME: + return order1.getMedicineName().compareTo(order2.getMedicineName()); + case Order.QUANTITY_LOWERCASE: + case CommandParameters.QUANTITY: + return Integer.compare(order1.getQuantity(), order2.getQuantity()); + case Order.DATE_LOWERCASE: + case CommandParameters.DATE: + return order1.getDate().compareTo(order2.getDate()); + case Order.STATUS_LOWERCASE: + case CommandParameters.STATUS: + return order1.getStatus().compareTo(order2.getStatus()); + default: + return 0; + } + } +} diff --git a/src/main/java/utilities/comparators/PrescriptionComparator.java b/src/main/java/utilities/comparators/PrescriptionComparator.java new file mode 100644 index 0000000000..88c9d7d6b2 --- /dev/null +++ b/src/main/java/utilities/comparators/PrescriptionComparator.java @@ -0,0 +1,58 @@ +package utilities.comparators; + +import command.CommandParameters; +import inventory.Prescription; + +import java.util.Comparator; + +//@@author alvintan01 + +/** + * Helps to sort the medicines based on the column provided. + */ +public class PrescriptionComparator implements Comparator { + private String column; + private Boolean isReversed; + + public PrescriptionComparator(String column, Boolean isReversed) { + this.column = column; + this.isReversed = isReversed; + } + + @Override + public int compare(Prescription prescription1, Prescription prescription2) { + Prescription prescription; + + if (isReversed) { // If the user chooses to sort in reverse order + prescription = prescription2; + prescription2 = prescription1; + prescription1 = prescription; + } + + switch (column) { + case Prescription.ID_LOWERCASE: + case CommandParameters.ID: + return Integer.compare(prescription1.getPrescriptionId(), prescription2.getPrescriptionId()); + case Prescription.NAME_LOWERCASE: + case CommandParameters.NAME: + return prescription1.getMedicineName().compareTo(prescription2.getMedicineName()); + case Prescription.QUANTITY_LOWERCASE: + case CommandParameters.QUANTITY: + return Integer.compare(prescription1.getQuantity(), prescription2.getQuantity()); + case Prescription.CUSTOMER_ID_LOWERCASE: + case CommandParameters.CUSTOMER_ID: + return prescription1.getCustomerId().compareTo(prescription2.getCustomerId()); + case Prescription.DATE_LOWERCASE: + case CommandParameters.DATE: + return prescription1.getDate().compareTo(prescription2.getDate()); + case Prescription.STAFF_LOWERCASE: + case CommandParameters.STAFF: + return prescription1.getStaff().compareTo(prescription2.getStaff()); + case Prescription.STOCK_ID_LOWERCASE: + case CommandParameters.STOCK_ID: + return Integer.compare(prescription1.getStockId(), prescription2.getStockId()); + default: + return 0; + } + } +} diff --git a/src/main/java/utilities/comparators/StockComparator.java b/src/main/java/utilities/comparators/StockComparator.java new file mode 100644 index 0000000000..c2adc4404f --- /dev/null +++ b/src/main/java/utilities/comparators/StockComparator.java @@ -0,0 +1,57 @@ +package utilities.comparators; + +import command.CommandParameters; +import inventory.Stock; + +import java.util.Comparator; + +//@@author alvintan01 + +/** + * Helps to sort the medicines based on the column provided. + */ +public class StockComparator implements Comparator { + private String column; + private Boolean isReversed; + + public StockComparator(String column, Boolean isReversed) { + this.column = column; + this.isReversed = isReversed; + } + + @Override + public int compare(Stock stock1, Stock stock2) { + Stock stock; + if (isReversed) { // If the user chooses to sort in reverse order + stock = stock2; + stock2 = stock1; + stock1 = stock; + } + + switch (column) { + case Stock.ID_LOWERCASE: + case CommandParameters.ID: + return Integer.compare(stock1.getStockId(), stock2.getStockId()); + case Stock.NAME_LOWERCASE: + case CommandParameters.NAME: + return stock1.getMedicineName().compareTo(stock2.getMedicineName()); + case Stock.PRICE_LOWERCASE: + case CommandParameters.PRICE: + return Double.compare(stock1.getPrice(), stock2.getPrice()); + case Stock.QUANTITY_LOWERCASE: + case CommandParameters.QUANTITY: + return Integer.compare(stock1.getQuantity(), stock2.getQuantity()); + case Stock.EXPIRY_DATE_LOWERCASE: + case CommandParameters.EXPIRY_DATE: + return stock1.getExpiry().compareTo(stock2.getExpiry()); + case Stock.DESCRIPTION_LOWERCASE: + case CommandParameters.DESCRIPTION: + return stock1.getDescription().compareTo(stock2.getDescription()); + case Stock.MAX_QUANTITY_LOWERCASE: + case CommandParameters.MAX_QUANTITY: + return Integer.compare(stock1.getMaxQuantity(), stock2.getMaxQuantity()); + default: + return 0; + } + } +} diff --git a/src/main/java/utilities/parser/CommandParser.java b/src/main/java/utilities/parser/CommandParser.java new file mode 100644 index 0000000000..bf666bde6a --- /dev/null +++ b/src/main/java/utilities/parser/CommandParser.java @@ -0,0 +1,217 @@ +package utilities.parser; + +import command.Command; +import command.ExitCommand; +import command.HelpCommand; +import command.PurgeCommand; +import command.order.ReceiveOrderCommand; +import command.prescription.AddPrescriptionCommand; +import command.prescription.ArchivePrescriptionCommand; +import command.prescription.DeletePrescriptionCommand; +import command.prescription.ListPrescriptionCommand; +import command.prescription.UpdatePrescriptionCommand; +import command.stock.AddStockCommand; +import command.stock.DeleteStockCommand; +import command.stock.ListStockCommand; +import command.stock.UpdateStockCommand; +import command.order.AddOrderCommand; +import command.order.ArchiveOrderCommand; +import command.order.DeleteOrderCommand; +import command.order.ListOrderCommand; +import command.order.UpdateOrderCommand; +import errors.InvalidCommandException; +import utilities.ui.Ui; + +import java.util.LinkedHashMap; + +import static command.CommandList.ADD; +import static command.CommandList.ADD_PRESCRIPTION; +import static command.CommandList.ADD_ORDER; +import static command.CommandList.ADD_STOCK; +import static command.CommandList.ARCHIVE; +import static command.CommandList.ARCHIVE_PRESCRIPTION; +import static command.CommandList.ARCHIVE_ORDER; +import static command.CommandList.DELETE; +import static command.CommandList.DELETE_PRESCRIPTION; +import static command.CommandList.DELETE_STOCK; +import static command.CommandList.DELETE_ORDER; +import static command.CommandList.EXIT; +import static command.CommandList.HELP; +import static command.CommandList.LIST; +import static command.CommandList.LIST_PRESCRIPTION; +import static command.CommandList.LIST_STOCK; +import static command.CommandList.LIST_ORDER; +import static command.CommandList.PURGE; +import static command.CommandList.RECEIVE; +import static command.CommandList.RECEIVE_ORDER; +import static command.CommandList.UPDATE; +import static command.CommandList.UPDATE_PRESCRIPTION; +import static command.CommandList.UPDATE_STOCK; +import static command.CommandList.UPDATE_ORDER; +import static utilities.parser.Mode.PRESCRIPTION; +import static utilities.parser.Mode.ORDER; +import static utilities.parser.Mode.STOCK; + +//@@author alvintan01 + +/** + * Helps to parse the commands given by the user as well as extract the parameters provided. + */ +public class CommandParser { + public CommandParser() { + } + + private static final String DELIMITER = "/"; + private static final String SPACE_DELIMITER = "\\s+"; + + /** + * Processes the user input into a Command Object. + * + * @param command Input provided by user. + * @param parametersString String parameter entered by user. + * @param mode The current mode of the program. + * @return A Command object. + * @throws InvalidCommandException If a command does not exist. + */ + public Command processCommand(String command, String parametersString, Mode mode) throws InvalidCommandException { + // Append user's command with mode + if (command.equals(ADD) || command.equals(LIST) || command.equals(UPDATE) + || command.equals(DELETE) || command.equals(ARCHIVE) || command.equals(RECEIVE)) { + command = command + mode.name().toLowerCase(); + } + + LinkedHashMap parameters = parseParameters(parametersString); + + switch (command) { + case ADD_PRESCRIPTION: + return new AddPrescriptionCommand(parameters); + case ADD_STOCK: + return new AddStockCommand(parameters); + case ADD_ORDER: + return new AddOrderCommand(parameters); + case ARCHIVE_ORDER: + return new ArchiveOrderCommand(parameters); + case ARCHIVE_PRESCRIPTION: + return new ArchivePrescriptionCommand(parameters); + case DELETE_PRESCRIPTION: + return new DeletePrescriptionCommand(parameters); + case DELETE_STOCK: + return new DeleteStockCommand(parameters); + case DELETE_ORDER: + return new DeleteOrderCommand(parameters); + case EXIT: + return new ExitCommand(); + case HELP: + return new HelpCommand(); + case LIST_PRESCRIPTION: + return new ListPrescriptionCommand(parameters); + case LIST_STOCK: + return new ListStockCommand(parameters); + case LIST_ORDER: + return new ListOrderCommand(parameters); + case PURGE: + return new PurgeCommand(); + case RECEIVE_ORDER: + return new ReceiveOrderCommand(parameters); + case UPDATE_STOCK: + return new UpdateStockCommand(parameters); + case UPDATE_PRESCRIPTION: + return new UpdatePrescriptionCommand(parameters); + case UPDATE_ORDER: + return new UpdateOrderCommand(parameters); + default: + throw new InvalidCommandException(); + } + } + + /** + * Splits the user input into command and command parameters. + * + * @param userInput String input from user. + * @return Array of string with size 2 with index 0 representing the command and index 1 representing the + * command parameters. + */ + public String[] parseCommand(String userInput) { + // Splits user input by spaces + String[] userInputSplit = userInput.trim().split(SPACE_DELIMITER, 2); + + assert (userInputSplit.length <= 2) : "Command extraction failed! More than 2 values were returned!"; + + String command = userInputSplit[0].toLowerCase(); + String commandParameters = ""; + if (userInputSplit.length > 1) { // Ensure command parameter exists + commandParameters = userInputSplit[1].toUpperCase(); + } + return new String[]{command, commandParameters}; + } + + /** + * Returns all the parameters entered as a LinkedHashMap. + * + * @param parameterString String of parameters. + * @return LinkedHashMap with parameter as key and parameter contents as value. + */ + public LinkedHashMap parseParameters(String parameterString) { + LinkedHashMap parameters = new LinkedHashMap<>(); + + if (parameterString.equals("")) { // Ensure parameter string is not empty + return parameters; + } + + String[] parameterSplit = parameterString.split(SPACE_DELIMITER); // Split by space + + String commandParameter = ""; + StringBuilder parameterContents = new StringBuilder(); + + for (String s : parameterSplit) { + if (s.contains(DELIMITER)) { + if (!commandParameter.equals("")) { // Ensure it is not the first iteration + // Add to linkedhashmap before resetting values + parameters.put(commandParameter, parameterContents.toString()); + } + + parameterContents = new StringBuilder(); // Reset the values + String[] commandSplit = s.split(DELIMITER); + + if (commandSplit.length != 0) { // Ensure '/' exists + commandParameter = commandSplit[0].toLowerCase(); + } + + if (commandSplit.length > 1) { + parameterContents = new StringBuilder(commandSplit[1]); + } + } else { // Add the rest of the string + parameterContents.append(" ").append(s); + } + } + parameters.put(commandParameter, parameterContents.toString()); // Add to linkedhashmap for the last parameter + return parameters; + } + + /** + * Helps to set the mode of the program. + * + * @param ui Reference to the UI object to print messages. + * @param command Command entered by the user. + * @param mode Current mode of the program. + * @return New mode requested by the user. + */ + public Mode changeMode(Ui ui, String command, Mode mode) { + Mode newMode = mode; + if (command.equalsIgnoreCase(STOCK.name()) && !mode.name().equalsIgnoreCase(STOCK.name())) { + newMode = STOCK; + ui.print("Mode has changed to STOCK."); + } else if (command.equalsIgnoreCase(Mode.PRESCRIPTION.name()) + && !mode.name().equalsIgnoreCase(PRESCRIPTION.name())) { + newMode = Mode.PRESCRIPTION; + ui.print("Mode has changed to PRESCRIPTION."); + } else if (command.equalsIgnoreCase(ORDER.name()) && !mode.name().equalsIgnoreCase(ORDER.name())) { + newMode = ORDER; + ui.print("Mode has changed to ORDER."); + } else { + ui.print("Already in " + mode.name() + " mode!"); + } + return newMode; + } + +} \ No newline at end of file diff --git a/src/main/java/utilities/parser/DateParser.java b/src/main/java/utilities/parser/DateParser.java new file mode 100644 index 0000000000..3a7f61cfdd --- /dev/null +++ b/src/main/java/utilities/parser/DateParser.java @@ -0,0 +1,61 @@ +package utilities.parser; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Calendar; +import java.util.Date; + +//@@author alvintan01 + +/** + * Contains the parser for date objects. + */ +public class DateParser { + public static final String INPUT_DATE_FORMAT = "d-M-yyyy"; + public static final String OUTPUT_DATE_FORMAT = "dd-MM-yyyy"; + + /** + * Helps to parse a string to a LocalDate object. + * + * @param date String date to be converted. + * @return LocalDate object representing date. + * @throws ParseException If date is invalid. + */ + public static Date stringToDate(String date) throws ParseException { + try { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(INPUT_DATE_FORMAT); + dateTimeFormatter.parse(date); // To check if date was in valid format + } catch (DateTimeParseException e) { + throw new ParseException("Unknown date", 0); + } + return new SimpleDateFormat(INPUT_DATE_FORMAT).parse(date); + } + + /** + * Helps to parse a LocalDate object to string. + * + * @param date Date object to be converted to string. + * @return String value of date. + */ + public static String dateToString(Date date) { + return new SimpleDateFormat(OUTPUT_DATE_FORMAT).format(date); + } + + /** + * Helps to remove time from date object. + * + * @param date Date object which time will be removed. + * @return Date object without time. + */ + public static Date removeTime(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } +} diff --git a/src/main/java/utilities/parser/FileParser.java b/src/main/java/utilities/parser/FileParser.java new file mode 100644 index 0000000000..026c7260f0 --- /dev/null +++ b/src/main/java/utilities/parser/FileParser.java @@ -0,0 +1,418 @@ +package utilities.parser; + +import errors.InvalidDataException; + +import java.text.ParseException; +import java.util.Date; +import java.util.HashSet; + +//@@author RemusTeo + +/** + * FileParser class handles all validation of input from data/stock.txt, data/order.txt, data/prescription.txt + */ +public class FileParser { + private static HashSet stockIds = new HashSet<>(); + private static HashSet orderIds = new HashSet<>(); + private static HashSet prescriptionIds = new HashSet<>(); + + /** + * Perform validation of Stock Id during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock Id of integer data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parseStockId(String[] splitStockDetails, int stockRow) throws InvalidDataException { + try { + int stockId = Integer.parseInt(splitStockDetails[0]); + if (stockId <= 0) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK ID [data/stock.txt]"); + } + if (stockIds.contains(stockId)) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK ID [data/stock.txt]"); + } + stockIds.add(stockId); + return stockId; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK ID [data/stock.txt]"); + } + } + + /** + * Perform validation of Stock name during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock name of String data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static String parseStockName(String[] splitStockDetails, int stockRow) throws InvalidDataException { + String stockName = splitStockDetails[1]; + if (stockName.equals("")) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK MEDICINE NAME [data/stock.txt]"); + } + return stockName; + } + + /** + * Perform validation of Stock price during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock price of double data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static Double parseStockPrice(String[] splitStockDetails, int stockRow) throws InvalidDataException { + try { + double stockPrice = Double.parseDouble(splitStockDetails[2]); + if (stockPrice < 0) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK PRICE [data/stock.txt]"); + } + return stockPrice; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK PRICE [data/stock.txt]"); + } + } + + /** + * Perform validation of Stock quantity during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock quantity of integer data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parseStockQuantity(String[] splitStockDetails, int stockRow) throws InvalidDataException { + try { + int stockQuantity = Integer.parseInt(splitStockDetails[3]); + if (stockQuantity < 0) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK QUANTITY [data/stock.txt]"); + } + return stockQuantity; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK QUANTITY [data/stock.txt]"); + } + } + + /** + * Perform validation of Stock expiry date during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock expiry date of Date data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static Date parseStockExpiry(String[] splitStockDetails, int stockRow) throws InvalidDataException { + try { + String dateExpiryStr = splitStockDetails[4]; + Date stockExpiry = DateParser.stringToDate(dateExpiryStr); + return stockExpiry; + } catch (ParseException e) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK EXPIRY DATE [data/stock.txt]"); + } + } + + /** + * Perform validation of Stock description during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock description of String data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static String parseStockDescription(String[] splitStockDetails, int stockRow) throws InvalidDataException { + String stockDescription = splitStockDetails[5]; + if (stockDescription.equals("")) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK DESCRIPTION [data/stock.txt]"); + } + return stockDescription; + } + + /** + * Perform validation of Stock max quantity during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock max quantity of integer data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parseStockMaxQuantity(String[] splitStockDetails, int stockRow) throws InvalidDataException { + try { + int stockMaxQuantity = Integer.parseInt(splitStockDetails[6]); + if (stockMaxQuantity < 0) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK MAX QUANTITY [data/stock.txt]"); + } + return stockMaxQuantity; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK MAX QUANTITY [data/stock.txt]"); + } + } + + /** + * Perform validation of Stock isDeleted field during parsing from file. + * + * @param splitStockDetails Stock details array fields split by delimiter '|'. + * @param stockRow Stock row identifier for use in error message. + * @return Stock isDeleted of boolean data type for creation of Stock object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static boolean parseStockIsDeleted(String[] splitStockDetails, int stockRow) throws InvalidDataException { + String isDeleted = splitStockDetails[7]; + if (isDeleted.equalsIgnoreCase("TRUE") || isDeleted.equalsIgnoreCase("FALSE")) { + boolean stockIsDeleted = Boolean.parseBoolean(splitStockDetails[7]); + return stockIsDeleted; + } else { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID STOCK ISDELETED [data/stock.txt]"); + } + } + + /** + * Perform validation of Order Id during parsing from file. + * + * @param splitOrderDetails Order details array fields split by delimiter '|'. + * @param orderRow Order row identifier for use in error message. + * @return Order Id of integer data type for creation of Order object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parseOrderId(String[] splitOrderDetails, int orderRow) throws InvalidDataException { + try { + int orderId = Integer.parseInt(splitOrderDetails[0]); + if (orderId <= 0) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER ID [data/order.txt]"); + } + if (orderIds.contains(orderId)) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER ID [data/order.txt]"); + } + orderIds.add(orderId); + return orderId; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER ID [data/order.txt]"); + } + } + + /** + * Perform validation of Order medication name during parsing from file. + * + * @param splitOrderDetails Order details array fields split by delimiter '|'. + * @param orderRow Order row identifier for use in error message. + * @return Order medication name of String data type for creation of Order object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static String parseOrderName(String[] splitOrderDetails, int orderRow) throws InvalidDataException { + String orderName = splitOrderDetails[1]; + if (orderName.equals("")) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER MEDICINE NAME [data/order.txt]"); + } + return orderName; + } + + /** + * Perform validation of Order quantity during parsing from file. + * + * @param splitOrderDetails Order details array fields split by delimiter '|'. + * @param orderRow Order row identifier for use in error message. + * @return Order quantity of integer data type for creation of Order object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parseOrderQuantity(String[] splitOrderDetails, int orderRow) throws InvalidDataException { + try { + int orderQuantity = Integer.parseInt(splitOrderDetails[2]); + if (orderQuantity <= 0) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER QUANTITY [data/order.txt]"); + } + return orderQuantity; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER QUANTITY [data/order.txt]"); + } + } + + /** + * Perform validation of Order date during parsing from file. + * + * @param splitOrderDetails Order details array fields split by delimiter '|'. + * @param orderRow Order row identifier for use in error message. + * @return Order date of Date data type for creation of Order object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static Date parseOrderDate(String[] splitOrderDetails, int orderRow) throws InvalidDataException { + try { + String orderDateStr = splitOrderDetails[3]; + Date orderDate = DateParser.stringToDate(orderDateStr); + return orderDate; + } catch (ParseException e) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER DATE [data/order.txt]"); + } + } + + /** + * Perform validation of Order status during parsing from file. + * + * @param splitOrderDetails Order details array fields split by delimiter '|'. + * @param orderRow Order row identifier for use in error message. + * @return Order status of boolean data type for creation of Order object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static boolean parseOrderStatus(String[] splitOrderDetails, int orderRow) throws InvalidDataException { + String orderStatusStr = splitOrderDetails[4]; + boolean orderStatus; + if (orderStatusStr.equalsIgnoreCase("DELIVERED")) { + orderStatus = true; + return orderStatus; + } else if (orderStatusStr.equalsIgnoreCase("PENDING")) { + orderStatus = false; + return orderStatus; + } else { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID ORDER STATUS [data/order.txt]"); + } + } + + /** + * Perform validation of Prescription Id during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription Id of integer data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parsePrescriptionId(String[] splitPrescriptionDetails, int prescriptionRow) + throws InvalidDataException { + try { + int prescriptionId = Integer.parseInt(splitPrescriptionDetails[0]); + if (prescriptionId <= 0) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION " + + "ID [data/prescription.txt]"); + } + if (prescriptionIds.contains(prescriptionId)) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION " + + "ID [data/prescription.txt]"); + } + prescriptionIds.add(prescriptionId); + return prescriptionId; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION " + + "ID [data/prescription.txt]"); + } + } + + /** + * Perform validation of Prescription medication name during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription medication name of String data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static String parsePrescriptionName(String[] splitPrescriptionDetails, int prescriptionRow) + throws InvalidDataException { + String prescriptionName = splitPrescriptionDetails[1]; + if (prescriptionName.equals("")) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIBED MEDICATION NAME " + + "[data/prescription.txt]"); + } + return prescriptionName; + } + + /** + * Perform validation of Prescription quantity during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription quantity of integer data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parsePrescriptionQuantity(String[] splitPrescriptionDetails, int prescriptionRow) throws + InvalidDataException { + try { + int prescriptionQuantity = Integer.parseInt(splitPrescriptionDetails[2]); + if (prescriptionQuantity <= 0) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION QUANTITY" + + " [data/prescription.txt]"); + } + return prescriptionQuantity; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION " + + "QUANTITY [data/prescription.txt]"); + } + } + + /** + * Perform validation of Prescription customer id during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription customer id of String data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static String parsePrescriptionCustomerId(String[] splitPrescriptionDetails, int prescriptionRow) throws + InvalidDataException { + String prescriptionCustomerId = splitPrescriptionDetails[3]; + if (prescriptionCustomerId.equals("")) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION CUSTOMER ID " + + "[data/prescription.txt]"); + } + return prescriptionCustomerId; + } + + /** + * Perform validation of Prescription date during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription date of Date data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static Date parsePrescriptionDate(String[] splitPrescriptionDetails, int prescriptionRow) + throws InvalidDataException { + try { + String prescriptionDateStr = splitPrescriptionDetails[4]; + Date prescriptionDate = DateParser.stringToDate(prescriptionDateStr); + return prescriptionDate; + } catch (ParseException e) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION DATE " + + "[data/prescription.txt]"); + } + } + + /** + * Perform validation of Prescription staff name during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription staff name of String data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static String parsePrescriptionStaff(String[] splitPrescriptionDetails, int prescriptionRow) throws + InvalidDataException { + String prescriptionStaff = splitPrescriptionDetails[5]; + if (prescriptionStaff.equals("")) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION STAFF NAME " + + "[data/prescription.txt]"); + } + return prescriptionStaff; + } + + /** + * Perform validation of Prescription stock id during parsing from file. + * + * @param splitPrescriptionDetails Prescription details array fields split by delimiter '|'. + * @param prescriptionRow Prescription row identifier for use in error message. + * @return Prescription stock id of integer data type for creation of Prescription object. + * @throws InvalidDataException Invalid data will trigger exception. + */ + public static int parsePrescriptionStockId(String[] splitPrescriptionDetails, int prescriptionRow) + throws InvalidDataException { + try { + int prescriptionStockId = Integer.parseInt(splitPrescriptionDetails[6]); + if (prescriptionStockId <= 0) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION STOCK ID " + + "[data/prescription.txt]"); + } + return prescriptionStockId; + } catch (NumberFormatException e) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID PRESCRIPTION STOCK ID " + + "[data/prescription.txt]"); + } + } +} diff --git a/src/main/java/utilities/parser/MedicineValidator.java b/src/main/java/utilities/parser/MedicineValidator.java new file mode 100644 index 0000000000..9a89b38532 --- /dev/null +++ b/src/main/java/utilities/parser/MedicineValidator.java @@ -0,0 +1,242 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Medicine; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Contains all the methods to validate if a Medicine's input parameters are valid. + */ +public abstract class MedicineValidator { + private static Logger logger = Logger.getLogger("MedicineValidator"); + + public MedicineValidator() { + } + + public abstract boolean containsInvalidParameterValues(Ui ui, LinkedHashMap parameters, + ArrayList medicines, String commandSyntax, + ArrayList invalidParameters); + + public abstract boolean isValidColumn(Ui ui, String columnName); + + /** + * Helps to check if the parameters and values required are provided by the user. + * + * @param ui Reference to the UI object to print messages. + * @param medicines Arraylist of all medicines. + * @param parameters Parameters entered in by the user. + * @param requiredParameters Parameters required by the command. + * @param optionalParameters Parameters that are optional. + * @param commandSyntax The command's valid syntax. + * @param requiresOptionalParameters Boolean value of whether command required optional parameters. + * @param validator Validator object used for validation checks. + * @return A boolean value indicating if the parameters and values required are entered by the user. + */ + public boolean containsInvalidParametersAndValues(Ui ui, ArrayList medicines, + LinkedHashMap parameters, + String[] requiredParameters, String[] optionalParameters, + String commandSyntax, boolean requiresOptionalParameters, + MedicineValidator validator) { + boolean isInvalidParameter = validator.containsInvalidParameters(ui, parameters, requiredParameters, + optionalParameters, commandSyntax, requiresOptionalParameters); + if (isInvalidParameter) { + logger.log(Level.WARNING, "Invalid parameters given by user"); + return true; + } + + int requiredParametersLength = requiredParameters.length; + int optionalParametersLength = optionalParameters.length; + // Combine both parameter array to check if optional parameter is valid + String[] mergedParameters = new String[requiredParametersLength + optionalParametersLength]; + System.arraycopy(requiredParameters, 0, mergedParameters, 0, requiredParametersLength); + System.arraycopy(optionalParameters, 0, mergedParameters, requiredParametersLength, optionalParametersLength); + + ArrayList invalidParameters = getInvalidParameters(parameters, mergedParameters); + + boolean isInvalidParameterValues = validator.containsInvalidParameterValues(ui, parameters, + medicines, commandSyntax, invalidParameters); + if (isInvalidParameterValues) { + logger.log(Level.WARNING, "Invalid parameters values given by user"); + return true; + } + + return false; + } + + /** + * Filter out the invalid parameters provided by user. + * + * @param parameters Parameters entered in by the user. + * @param mergedParameters Parameters that are both required and optional. + * @return ArrayList of invalid parameters. + */ + private ArrayList getInvalidParameters(LinkedHashMap parameters, + String[] mergedParameters) { + ArrayList invalidParameters = new ArrayList<>(); + for (String parameter : parameters.keySet()) { + boolean found = false; + for (String mergedParameter : mergedParameters) { + if (parameter.equalsIgnoreCase(mergedParameter)) { + found = true; + break; + } + } + if (found) { + continue; + } + invalidParameters.add(parameter); + } + return invalidParameters; + } + + + /** + * Helps to check if the parameters required are provided by the user. + * + * @param ui Reference to the UI object to print messages. + * @param parameters Parameters entered in by the user. + * @param requiredParameters Parameters required by the command. + * @param optionalParameters Parameters that are optional. + * @param commandSyntax The command's valid syntax. + * @param requiresOptionalParameters Boolean value of whether command required optional parameters. + * @return A boolean value indicating if the parameters required are entered by the user. + */ + public boolean containsInvalidParameters(Ui ui, LinkedHashMap parameters, + String[] requiredParameters, String[] optionalParameters, + String commandSyntax, boolean requiresOptionalParameters) { + + // User did not provide parameters all the parameters + /*if (parameters.keySet().size() < requiredParametersLength) { + ui.printInvalidParameter("", commandSyntax); + return true; + }*/ + boolean providedRequiredParameter = true; + ArrayList missingParametersList = new ArrayList<>(); + for (String requiredParameter : requiredParameters) { + if (!parameters.containsKey(requiredParameter)) { // Checks if required parameters are found + missingParametersList.add(requiredParameter); + providedRequiredParameter = false; + } + } + + if (!providedRequiredParameter) { + ui.printRequiredParameters(missingParametersList, commandSyntax); + return true; + } + + int requiredParametersLength = requiredParameters.length; + int optionalParametersLength = optionalParameters.length; + + // Optional parameters not provided considered valid + if (optionalParameters == null || optionalParametersLength == 0) { + return false; + } + + int emptyOptionalFieldCount = parameters.size() - requiredParametersLength; + if (emptyOptionalFieldCount <= 0 && requiresOptionalParameters) { + ui.print("Please provide at least one optional field!"); + ui.printCommandSyntax(commandSyntax); + return true; + } + + // Combine both parameter array to check if optional parameter is valid + String[] mergedParameters = new String[requiredParametersLength + optionalParametersLength]; + System.arraycopy(requiredParameters, 0, mergedParameters, 0, requiredParametersLength); + System.arraycopy(optionalParameters, 0, mergedParameters, requiredParametersLength, optionalParametersLength); + + for (String parameter : parameters.keySet()) { + boolean isValid = false; + for (String mergedParameter : mergedParameters) { + if (mergedParameter.equalsIgnoreCase(parameter)) { + isValid = true; + break; + } + } + if (!isValid) { + ui.print("Please enter a valid optional parameter!"); + ui.printCommandSyntax(commandSyntax); + return true; + } + } + + return false; + } + + /** + * Checks if a medicine name is empty. + * + * @param ui Reference to the UI object to print messages. + * @param name Medicine name to be checked. + * @return Boolean value indicating if medicine name is valid. + */ + public boolean isValidName(Ui ui, String name) { + if (name.equals("")) { + ui.print("Medication name cannot be empty!"); + return false; + } + return true; + } + + /** + * Checks if a medicine quantity is valid. + * + * @param ui Reference to the UI object to print messages. + * @param quantityString Quantity of the medicine. + * @return Boolean value indicating if medicine quantity is valid. + */ + public boolean isValidQuantity(Ui ui, String quantityString) { + try { + int quantity = Integer.parseInt(quantityString); + if (quantity < 0) { + throw new Exception(); + } + return true; + } catch (Exception e) { + ui.print("Invalid quantity provided!"); + } + return false; + } + + /** + * Checks if name parameter exists. + * + * @param parameters Parameters entered in by the user. + * @param medicineName Medicine name of the given object. + * @return A boolean value to return whether a parameter exist. + */ + public boolean hasNameParamChecker(LinkedHashMap parameters, String medicineName) { + boolean hasNameParam = parameters.containsKey(CommandParameters.NAME); + if (hasNameParam) { + String currentName = medicineName; + String updatedName = parameters.get(CommandParameters.NAME); + if (updatedName.equalsIgnoreCase(currentName)) { + hasNameParam = false; + } + } + return hasNameParam; + } + + /** + * Checks if given quantity and stored quantity are the same. + * + * @param parameters Parameters entered in by the user. + * @param quantity Current quantity in stocks. + * @return A boolean value to return whether a parameter exist. + */ + public boolean hasQuantityParamChecker(LinkedHashMap parameters, int quantity) { + boolean hasQuantityParam = parameters.containsKey(CommandParameters.QUANTITY); + if (hasQuantityParam) { + int currentQuantity = quantity; + int updatedQuantity = Integer.parseInt(parameters.get(CommandParameters.QUANTITY)); + if (currentQuantity == updatedQuantity) { + hasQuantityParam = false; + } + } + return hasQuantityParam; + } +} diff --git a/src/main/java/utilities/parser/Mode.java b/src/main/java/utilities/parser/Mode.java new file mode 100644 index 0000000000..1b1a26bd4d --- /dev/null +++ b/src/main/java/utilities/parser/Mode.java @@ -0,0 +1,12 @@ +package utilities.parser; + +//@@author alvintan01 + +/** + * Contains all modes available in the program. + */ +public enum Mode { + STOCK, + PRESCRIPTION, + ORDER +} diff --git a/src/main/java/utilities/parser/OrderManager.java b/src/main/java/utilities/parser/OrderManager.java new file mode 100644 index 0000000000..99e61fcded --- /dev/null +++ b/src/main/java/utilities/parser/OrderManager.java @@ -0,0 +1,78 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Medicine; +import inventory.Order; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +//@@author a-tph + +/** + * Manages medicines that are order objects. + */ +public class OrderManager { + + /** + * Extracts the order object for a given order id. + * + * @param parameters LinkedHashMap Key-Value set for parameter and user specified parameter value. + * @param medicines Arraylist of all medicines. + * @return Stock object of the provided order id by user + */ + public static Order extractOrderObject(LinkedHashMap parameters, ArrayList medicines) { + int orderId = Integer.parseInt(parameters.get(CommandParameters.ID)); + Order order = null; + for (Medicine medicine : medicines) { + if (medicine instanceof Order && orderId == ((Order) medicine).getOrderId()) { + order = (Order) medicine; + } + } + assert (order != null) : "Expected an order object but none extracted"; + return order; + } + + /** + * Retrieves the total order quantity for order with same name. + * + * @param medicines Arraylist of medicines. + * @param name Medicine name. + * @return Total order quantity for the medicine. + */ + public static int getTotalOrderQuantity(ArrayList medicines, String name) { + int existingQuantity = 0; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Order) || ((Order) medicine).isDelivered()) { + continue; + } + boolean isSameMedicineName = medicine.getMedicineName().equalsIgnoreCase(name); + if (isSameMedicineName) { + existingQuantity += medicine.getQuantity(); + } + } + return existingQuantity; + } + + /** + * Extracts the filtered orders for stocks with same name. + * + * @param medicines Arraylist of all medicines. + * @param orderName Medicine name for a given order. + * @return ArrayList of filteredStocks of the same stock name. + */ + public static ArrayList getFilteredOrdersByName(ArrayList medicines, String orderName) { + ArrayList filteredOrders = new ArrayList<>(); + for (Medicine medicine : medicines) { + boolean isOrderInstance = medicine instanceof Order; + if (isOrderInstance) { + boolean isSameName = medicine.getMedicineName().equalsIgnoreCase(orderName); + boolean isPending = !(((Order) medicine).isDelivered()); + if (isSameName && isPending) { + filteredOrders.add((Order) medicine); + } + } + } + return filteredOrders; + } +} diff --git a/src/main/java/utilities/parser/OrderValidator.java b/src/main/java/utilities/parser/OrderValidator.java new file mode 100644 index 0000000000..00390ada53 --- /dev/null +++ b/src/main/java/utilities/parser/OrderValidator.java @@ -0,0 +1,176 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Medicine; +import inventory.Order; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; + +//@@author deonchung + +/** + * Contains all the methods to validate if an Order's input parameters are valid. + */ +public class OrderValidator extends MedicineValidator { + public OrderValidator() { + } + + /** + * Checks if parameter values are valid for Order objects. + * + * @param ui Reference to the UI object to print messages. + * @param parameters LinkedHashMap Key-Value set for parameter and user specified parameter value. + * @param medicines Arraylist of all medicines. + * @param commandSyntax The command's valid syntax. + * @param invalidParameters Parameters to be ignored for checking. + * @return A boolean value indicating whether parameter values are valid. + */ + @Override + public boolean containsInvalidParameterValues(Ui ui, LinkedHashMap parameters, + ArrayList medicines, String commandSyntax, + ArrayList invalidParameters) { + for (String parameter : parameters.keySet()) { + boolean ignore = false; + for (String invalidParameter : invalidParameters) { + if (parameter.equalsIgnoreCase(invalidParameter)) { + ignore = true; + break; + } + } + if (ignore) { + continue; + } + boolean isValid = false; + String parameterValue = parameters.get(parameter); + + switch (parameter) { + case CommandParameters.ID: + isValid = isValidOrderId(ui, parameterValue, medicines); + break; + case CommandParameters.NAME: + isValid = isValidName(ui, parameterValue); + break; + case CommandParameters.QUANTITY: + isValid = isValidQuantity(ui, parameterValue); + break; + case CommandParameters.DATE: + isValid = isValidDate(ui, parameterValue); + break; + case CommandParameters.STATUS: + isValid = isValidStatus(ui, parameterValue); + break; + case CommandParameters.SORT: + // Fallthrough + case CommandParameters.REVERSED_SORT: + isValid = isValidColumn(ui, parameterValue); + break; + default: + ui.printInvalidParameter(parameter, commandSyntax); + break; + } + if (!isValid) { + return true; + } + } + return false; + } + + /** + * Checks if the given order id is valid. + * + * @param ui Reference to the UI object to print messages. + * @param oid ID of the order to be checked. + * @param medicines List of all medicines. + * @return Boolean value indicating if order ID is valid. + */ + public boolean isValidOrderId(Ui ui, String oid, ArrayList medicines) { + try { + int orderId = Integer.parseInt(oid); + if (orderId <= 0 || orderId > Order.getOrderCount()) { + throw new Exception(); + } + boolean orderExist = false; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Order)) { + continue; + } + Order order = (Order) medicine; + if (order.getOrderId() == orderId) { + orderExist = true; + break; + } + } + if (!orderExist) { + throw new Exception(); + } + return true; + } catch (Exception e) { + ui.print("Invalid order id provided!"); + } + return false; + } + + /** + * Checks if a medicine order date is valid. + * + * @param ui Reference to the UI object to print messages. + * @param dateString Date of the medicine. + * @return Boolean value indicating if medicine expiry date is valid. + */ + public boolean isValidDate(Ui ui, String dateString) { + try { + DateParser.stringToDate(dateString); + Date date = DateParser.stringToDate(dateString); + Date todayDate = new Date(); + String todayDateString = DateParser.dateToString(todayDate); + todayDate = DateParser.stringToDate(todayDateString); + if (date.after(todayDate)) { + ui.print("Invalid date! Input date cannot be after today's date."); + return false; + } + return true; + } catch (Exception e) { + ui.print("Invalid date! Ensure date is in " + DateParser.OUTPUT_DATE_FORMAT + "."); + } + return false; + } + + /** + * Checks if a medicine order status is valid. + * + * @param ui Reference to the UI object to print messages. + * @param statusString Status of medicine order. + * @return Boolean value indicating if medicine expiry date is valid. + */ + public boolean isValidStatus(Ui ui, String statusString) { + if (statusString.equalsIgnoreCase("PENDING") + || statusString.equalsIgnoreCase("DELIVERED")) { + return true; + } + ui.print("Invalid status! Ensure status is either PENDING or DELIVERED"); + return false; + } + + /** + * Checks if a order column/alias exists. + * + * @param ui Reference to the UI object to print messages. + * @param columnName Column name/alias to be validated. + * @return Boolean value indicating if column name is value. + */ + public boolean isValidColumn(Ui ui, String columnName) { + String[] columnAlias = new String[]{CommandParameters.ID, CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.DATE, CommandParameters.STATUS}; + if (Arrays.asList(Order.COLUMNS).contains(columnName.toUpperCase()) || Arrays.asList(columnAlias) + .contains(columnName.toLowerCase())) { + return true; + } + ui.print("Invalid column name/alias! Column names can only be " + Arrays.toString(Order.COLUMNS) + " and " + + "the respective aliases are " + Arrays.toString(columnAlias) + "."); + return false; + } +} diff --git a/src/main/java/utilities/parser/PrescriptionManager.java b/src/main/java/utilities/parser/PrescriptionManager.java new file mode 100644 index 0000000000..d8d2fa9101 --- /dev/null +++ b/src/main/java/utilities/parser/PrescriptionManager.java @@ -0,0 +1,196 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Prescription; +import inventory.Medicine; +import inventory.Stock; +import utilities.comparators.StockComparator; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; + +//@@author a-tph + +/** + * Manages medicines that are prescription objects. + */ +public class PrescriptionManager { + + /** + * Extracts the prescription object for a given prescription id. + * + * @param parameters LinkedHashMap Key-Value set for parameter and user specified parameter value. + * @param medicines Arraylist of all medicines. + * @return Stock object of the provided order id by user + */ + public static Prescription extractPrescriptionObject(LinkedHashMap parameters, + ArrayList medicines) { + int orderId = Integer.parseInt(parameters.get(CommandParameters.ID)); + Prescription prescription = null; + for (Medicine medicine : medicines) { + if (medicine instanceof Prescription && orderId == ((Prescription) medicine).getPrescriptionId()) { + prescription = (Prescription) medicine; + } + } + assert (prescription != null) : "Expected a prescription object but none extracted"; + return prescription; + } + + /** + * Handles restoration of stock of a prescribed medication. + * + * @param targetRestoreStock The target stock object to restore quantity. + * @param restoreQuantity The quantity of the medication. + */ + public static void restoreStock(Stock targetRestoreStock, int restoreQuantity) { + targetRestoreStock.setDeleted(false); + targetRestoreStock.setQuantity(restoreQuantity); + } + + /** + * Handles dispensing more stocks of a prescribed medication of different names. + * + * @param medicines Arraylist of all medicines. + * @param targetPrescriptionStocks The target stock array list to prescribe quantity. + * @param prescribeQuantity The quantity to prescribe from the stocks. + * @param customerId The customer Id of the associated prescription object. + * @param date Date of prescription. + * @param staffName The staff Id of the associated prescription object. + * @returns ArrayList of updated prescriptions. + */ + public static ArrayList prescribeStock(ArrayList medicines, + ArrayList targetPrescriptionStocks, + int prescribeQuantity, + String customerId, Date date, String staffName) { + ArrayList newPrescriptions = new ArrayList<>(); + targetPrescriptionStocks.sort(new StockComparator(CommandParameters.EXPIRY_DATE, false)); + while (prescribeQuantity != 0) { + for (Stock stock : targetPrescriptionStocks) { + Prescription prescription; + if (stock.getQuantity() <= prescribeQuantity) { + prescription = new Prescription(stock.getMedicineName(), stock.getQuantity(), customerId, date, + staffName, stock.getStockId()); + newPrescriptions.add(prescription); + prescribeQuantity -= stock.getQuantity(); + stock.setQuantity(0); + } else { + int remainingStocks = stock.getQuantity() - prescribeQuantity; + prescription = new Prescription(stock.getMedicineName(), prescribeQuantity, customerId, date, + staffName, stock.getStockId()); + newPrescriptions.add(prescription); + stock.setQuantity(remainingStocks); + prescribeQuantity = 0; + break; + } + } + } + + // Updating quantity in stocks + for (Stock targetStock : targetPrescriptionStocks) { + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Stock stock = (Stock) medicine; + boolean isSameStockId = stock.getStockId() == targetStock.getStockId(); + boolean isDeleted = stock.isDeleted(); + if (isSameStockId && !isDeleted) { + medicine.setQuantity(targetStock.getQuantity()); + } + } + } + return newPrescriptions; + } + + /** + * Extracts the date value if it is provided. + * + * @param parameters Parameters entered in by the user. + * @param storedDate Date field currently in the prescription. + * @return The date to be updated. + */ + public static Date getUpdatedDate(LinkedHashMap parameters, Date storedDate) { + boolean hasDateParam = parameters.containsKey(CommandParameters.DATE); + Date date = null; + if (hasDateParam) { + try { + date = DateParser.stringToDate(parameters.get(CommandParameters.DATE)); + } catch (ParseException e) { + e.printStackTrace(); + } + } else { + date = storedDate; + } + assert date != null : "Date should not be null"; + return date; + } + + /** + * Extracts the Customer Id if it is provided. + * + * @param parameters Parameters entered in by the user. + * @param storedCustomerId Customer Id field currently in the prescription. + * @return The Customer Id to be updated. + */ + public static String getUpdatedCustomerId(LinkedHashMap parameters, String storedCustomerId) { + boolean hasCustomerParam = parameters.containsKey(CommandParameters.CUSTOMER_ID); + String customerId; + if (hasCustomerParam) { + customerId = parameters.get(CommandParameters.CUSTOMER_ID); + } else { + customerId = storedCustomerId; + } + return customerId; + } + + /** + * Extracts the Staff information if it is provided. + * + * @param parameters Parameters entered in by the user. + * @param storedStaff Staff field currently in the prescription. + * @return The Staff to be updated. + */ + public static String getUpdatedStaff(LinkedHashMap parameters, String storedStaff) { + boolean hasStaffParam = parameters.containsKey(CommandParameters.STAFF); + String staffName; + if (hasStaffParam) { + staffName = parameters.get(CommandParameters.STAFF); + } else { + staffName = storedStaff; + } + return staffName; + } + + //@@author deonchung + + /** + * Retrieves the total stock quantity for medicine with same name that has not expired. + * + * @param medicines Arraylist of medicines. + * @param name Medicine name. + * @param prescribeDate Date of Prescription. + * @return Total stock quantity for the medicine that has not expired. + */ + public static int getNotExpiredStockQuantity(ArrayList medicines, String name, Date prescribeDate) { + int existingQuantity = 0; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + boolean isSameMedicineName = medicine.getMedicineName().equalsIgnoreCase(name); + boolean isDeleted = ((Stock) medicine).isDeleted(); + Date existingExpiry = ((Stock) medicine).getExpiry(); + String expiryString = DateParser.dateToString(existingExpiry); + String prescribeDateString = DateParser.dateToString(prescribeDate); + boolean isNotExpired = (existingExpiry.after(prescribeDate) || prescribeDateString.equals(expiryString)); + + if (isSameMedicineName && !isDeleted && isNotExpired) { + existingQuantity += medicine.getQuantity(); + } + } + return existingQuantity; + } + +} diff --git a/src/main/java/utilities/parser/PrescriptionValidator.java b/src/main/java/utilities/parser/PrescriptionValidator.java new file mode 100644 index 0000000000..d9eb8bea6e --- /dev/null +++ b/src/main/java/utilities/parser/PrescriptionValidator.java @@ -0,0 +1,199 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Prescription; +import inventory.Medicine; +import utilities.ui.Ui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; + +//@@author deonchung + +/** + * Contains all the methods to validate if a Prescription input parameters are valid. + */ +public class PrescriptionValidator extends MedicineValidator { + public PrescriptionValidator() { + } + + /** + * Checks if parameter values are valid for Prescription objects. + * + * @param ui Reference to the UI object to print messages. + * @param parameters LinkedHashMap Key-Value set for parameter and user specified parameter value. + * @param medicines Arraylist of all medicines. + * @param commandSyntax The command's valid syntax. + * @param invalidParameters Parameters to be ignored for checking. + * @return A boolean value indicating whether parameter values are valid. + */ + @Override + public boolean containsInvalidParameterValues(Ui ui, LinkedHashMap parameters, + ArrayList medicines, String commandSyntax, + ArrayList invalidParameters) { + for (String parameter : parameters.keySet()) { + boolean ignore = false; + for (String invalidParameter : invalidParameters) { + if (parameter.equalsIgnoreCase(invalidParameter)) { + ignore = true; + break; + } + } + if (ignore) { + continue; + } + boolean isValid = false; + String parameterValue = parameters.get(parameter); + + switch (parameter) { + case CommandParameters.ID: + isValid = isValidPrescriptionId(ui, parameterValue, medicines); + break; + case CommandParameters.NAME: + isValid = isValidName(ui, parameterValue); + break; + case CommandParameters.QUANTITY: + isValid = isValidQuantity(ui, parameterValue); + break; + case CommandParameters.CUSTOMER_ID: + isValid = isValidCustomerId(ui, parameterValue); + break; + case CommandParameters.DATE: + isValid = isValidDate(ui, parameterValue); + break; + case CommandParameters.STAFF: + isValid = isValidStaffName(ui, parameterValue); + break; + case CommandParameters.STOCK_ID: + StockValidator stockValidator = new StockValidator(); + isValid = stockValidator.isValidStockId(ui, parameterValue, medicines); + break; + case CommandParameters.SORT: + // Fallthrough + case CommandParameters.REVERSED_SORT: + isValid = isValidColumn(ui, parameterValue); + break; + default: + ui.printInvalidParameter(parameter, commandSyntax); + break; + } + if (!isValid) { + return true; + } + } + return false; + } + + /** + * Checks if a prescription ID is valid. + * + * @param ui Reference to the UI object to print messages. + * @param id ID of the prescription record to be checked. + * @param medicines List of all medicines. + * @return Boolean value indicating if Prescription ID is valid. + */ + public boolean isValidPrescriptionId(Ui ui, String id, ArrayList medicines) { + try { + int prescriptionId = Integer.parseInt(id); + if (prescriptionId <= 0 || prescriptionId > Prescription.getPrescriptionCount()) { + throw new Exception(); + } + boolean prescriptionExist = false; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Prescription)) { + continue; + } + Prescription prescription = (Prescription) medicine; + if (prescription.getPrescriptionId() == prescriptionId) { + prescriptionExist = true; + break; + } + } + if (!prescriptionExist) { + ui.print("Invalid prescription id provided!"); + return false; + } + return true; + } catch (Exception e) { + ui.print("Invalid prescription id provided!"); + } + return false; + } + + /** + * Checks if a customer ID is valid. + * + * @param ui Reference to the UI object to print messages. + * @param customerId Customer ID to be checked. + * @return Boolean value indicating if Customer ID is valid. + */ + public boolean isValidCustomerId(Ui ui, String customerId) { + if (customerId.equals("")) { + ui.print("Customer ID cannot be empty!"); + return false; + } + return true; + } + + /** + * Checks if a Staff Name is valid. + * + * @param ui Reference to the UI object to print messages. + * @param staffName Staff Name to be checked. + * @return Boolean value indicating if Staff Name is valid. + */ + public boolean isValidStaffName(Ui ui, String staffName) { + if (staffName.equals("")) { + ui.print("Staff name cannot be empty!"); + return false; + } + return true; + } + + /** + * Checks if a prescription column/alias exists. + * + * @param ui Reference to the UI object to print messages. + * @param columnName Column name/alias to be validated. + * @return Boolean value indicating column is valid. + */ + public boolean isValidColumn(Ui ui, String columnName) { + String[] columnAlias = new String[]{CommandParameters.ID, CommandParameters.NAME, CommandParameters.QUANTITY, + CommandParameters.CUSTOMER_ID, CommandParameters.DATE, CommandParameters.STAFF, + CommandParameters.STOCK_ID}; + if (Arrays.asList(Prescription.COLUMNS).contains(columnName.toUpperCase()) || Arrays.asList(columnAlias) + .contains(columnName.toLowerCase())) { + return true; + } + ui.print("Invalid column name/alias! Column names can only be " + Arrays.toString(Prescription.COLUMNS) + " and" + + " the respective aliases are " + Arrays.toString(columnAlias) + "."); + return false; + } + + /** + * Checks if a prescription date is valid. + * + * @param ui Reference to the UI object to print messages. + * @param dateString Date of the prescription. + * @return Boolean value indicating if the date is valid. + */ + public boolean isValidDate(Ui ui, String dateString) { + try { + Date date = DateParser.stringToDate(dateString); + Date todayDate = new Date(); + String todayDateString = DateParser.dateToString(todayDate); + todayDate = DateParser.stringToDate(todayDateString); + if (date.after(todayDate)) { + ui.print("Invalid date! Input date cannot be after today's date."); + return false; + } + return true; + } catch (Exception e) { + ui.print("Invalid date! Ensure date is in " + DateParser.OUTPUT_DATE_FORMAT + "."); + } + return false; + } + +} diff --git a/src/main/java/utilities/parser/StockManager.java b/src/main/java/utilities/parser/StockManager.java new file mode 100644 index 0000000000..4c75f8dcb1 --- /dev/null +++ b/src/main/java/utilities/parser/StockManager.java @@ -0,0 +1,162 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Medicine; +import inventory.Stock; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; + +//@@author a-tph + +/** + * Manages medicines that are stock objects. + */ +public class StockManager { + + /** + * Retrieves the total stock quantity for medicine with same name. + * + * @param medicines Arraylist of medicines. + * @param name Medicine name. + * @return Total stock quantity for the medicine. + */ + public static int getTotalStockQuantity(ArrayList medicines, String name) { + int existingQuantity = 0; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + boolean isSameMedicineName = medicine.getMedicineName().equalsIgnoreCase(name); + boolean isDeleted = ((Stock) medicine).isDeleted(); + if (isSameMedicineName && !isDeleted) { + existingQuantity += medicine.getQuantity(); + } + } + return existingQuantity; + } + + /** + * Retrieves the maximum stock quantity for medicine with same name. + * + * @param medicines Arraylist of medicines. + * @param name Medicine name. + * @return Total maximum stock quantity for the medicine. + */ + public static int getMaxStockQuantity(ArrayList medicines, String name) { + int existingMaxQuantity = 0; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + boolean isSameMedicineName = medicine.getMedicineName().equalsIgnoreCase(name); + boolean isDeleted = ((Stock) medicine).isDeleted(); + if (isSameMedicineName && !isDeleted) { + existingMaxQuantity = ((Stock) medicine).getMaxQuantity(); + break; + } + } + assert (existingMaxQuantity > 0) : "Invalid max quantity"; + return existingMaxQuantity; + } + + /** + * Extracts the stock object for a given stock id. + * + * @param parameters HashMap Key-Value set for parameter and user specified parameter value. + * @param medicines Arraylist of all medicines. + * @return Stock object of the provided stock id by user + */ + public static Stock extractStockObject(HashMap parameters, ArrayList medicines) { + int stockId = Integer.parseInt(parameters.get(CommandParameters.ID)); + Stock stock = null; + for (Medicine medicine : medicines) { + if (medicine instanceof Stock && stockId == ((Stock) medicine).getStockId()) { + stock = (Stock) medicine; + } + } + assert (stock != null) : "Expected a stock object but none extracted"; + assert (stock.isDeleted() == false) : "Stock object should not be deleted"; + return stock; + } + + /** + * Extracts the stock object by a given medicine name and stock Id. + * + * @param medicines Arraylist of all medicines. + * @param name Name of the medicine. + * @param stockId Stock Id of the medicine. + * @return Stock object of the provided stock id by user + */ + public static Stock extractStockObject(ArrayList medicines, String name, int stockId) { + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Stock stock = (Stock) medicine; + boolean isSameName = name.equalsIgnoreCase(stock.getMedicineName()); + boolean isSameId = stockId == stock.getStockId(); + if (isSameName && isSameId) { + return stock; + } + } + return null; + } + + /** + * Extracts the filtered stocks for stocks with same name. + * + * @param medicines Arraylist of all medicines. + * @param stockName Stock name for a given stock. + * @return ArrayList of filteredStocks of the same stock name. + */ + public static ArrayList getFilteredStocksByName(ArrayList medicines, String stockName) { + ArrayList filteredStocks = new ArrayList<>(); + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + boolean isSameName = medicine.getMedicineName().equalsIgnoreCase(stockName); + boolean isDeleted = ((Stock) medicine).isDeleted(); + if (isSameName && !isDeleted) { + filteredStocks.add((Stock) medicine); + } + } + return filteredStocks; + } + + /** + * Extracts the unexpired filtered stock for stocks with same name. + * + * @param medicines Arraylist of all medicines. + * @param stockName Stock name for a given stock. + * @return ArrayList of filteredStocks of the same stock name. + */ + public static ArrayList getUnexpiredFilteredStocksByName(ArrayList medicines, String stockName) { + ArrayList filteredStocks = new ArrayList<>(); + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Date currentDate = new Date(); + String currentDateString = DateParser.dateToString(currentDate); + + try { + currentDate = DateParser.stringToDate(currentDateString); + } catch (ParseException e) { + e.printStackTrace(); + } + + boolean isSameName = medicine.getMedicineName().equalsIgnoreCase(stockName); + boolean isDeleted = ((Stock) medicine).isDeleted(); + boolean isExpired = ((Stock) medicine).getExpiry().before(currentDate); + if (isSameName && !isDeleted && !isExpired) { + filteredStocks.add((Stock) medicine); + } + } + return filteredStocks; + } + +} diff --git a/src/main/java/utilities/parser/StockValidator.java b/src/main/java/utilities/parser/StockValidator.java new file mode 100644 index 0000000000..9f89345a20 --- /dev/null +++ b/src/main/java/utilities/parser/StockValidator.java @@ -0,0 +1,281 @@ +package utilities.parser; + +import command.CommandParameters; +import inventory.Medicine; +import inventory.Stock; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; + +/** + * Contains all the methods to validate if a Medicine's input parameters are valid. + */ +public class StockValidator extends MedicineValidator { + public StockValidator() { + } + + /** + * Checks if parameter values are valid for Stock objects. + * + * @param ui Reference to the UI object passed by Main to print messages. + * @param parameters LinkedHashMap Key-Value set for parameter and user specified parameter value. + * @param medicines Arraylist of all medicines. + * @param commandSyntax The command's valid syntax. + * @param invalidParameters Parameters to be ignored for checking. + * @return A boolean value indicating whether parameter values are valid. + */ + @Override + public boolean containsInvalidParameterValues(Ui ui, LinkedHashMap parameters, + ArrayList medicines, String commandSyntax, + ArrayList invalidParameters) { + for (String parameter : parameters.keySet()) { + boolean ignore = false; + for (String invalidParameter : invalidParameters) { + if (parameter.equalsIgnoreCase(invalidParameter)) { + ignore = true; + break; + } + } + if (ignore) { + continue; + } + boolean isValid = false; + String parameterValue = parameters.get(parameter); + + switch (parameter) { + case CommandParameters.ID: + isValid = isValidStockId(ui, parameterValue, medicines); + break; + case CommandParameters.NAME: + isValid = isValidName(ui, parameterValue); + break; + case CommandParameters.PRICE: + isValid = isValidPrice(ui, parameterValue); + break; + case CommandParameters.LOW: + case CommandParameters.QUANTITY: + isValid = isValidQuantity(ui, parameterValue); + break; + case CommandParameters.EXPIRING: + // Fallthrough + case CommandParameters.EXPIRY_DATE: + isValid = isValidExpiry(ui, parameterValue); + break; + case CommandParameters.DESCRIPTION: + isValid = isValidDescription(ui, parameterValue); + break; + case CommandParameters.MAX_QUANTITY: + isValid = isValidMaxQuantity(ui, parameterValue); + break; + case CommandParameters.SORT: + // Fallthrough + case CommandParameters.REVERSED_SORT: + isValid = isValidColumn(ui, parameterValue); + break; + default: + ui.printInvalidParameter(parameter, commandSyntax); + break; + } + if (!isValid) { + return true; + } + } + return false; + } + + /** + * Checks if the given stock id is valid. + * + * @param ui Reference to the UI object to print messages. + * @param id ID of the medicine to be checked. + * @param medicines List of all medicines. + * @return Boolean value indicating if medicine ID is valid. + */ + public boolean isValidStockId(Ui ui, String id, ArrayList medicines) { + try { + int stockId = Integer.parseInt(id); + if (stockId <= 0 || stockId > Stock.getStockCount()) { + throw new Exception(); + } + boolean stockExist = false; + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Stock stock = (Stock) medicine; + if (stock.getStockId() == stockId && !stock.isDeleted()) { + stockExist = true; + break; + } + } + if (!stockExist) { + ui.print("Invalid stock id provided!"); + return false; + } + return true; + } catch (Exception e) { + ui.print("Invalid stock id provided!"); + } + return false; + } + + /** + * Checks if a medicine price is valid. + * + * @param ui Reference to the UI object to print messages. + * @param priceString Price of the medicine to be checked. + * @return Boolean value indicating if medicine price is valid. + */ + public boolean isValidPrice(Ui ui, String priceString) { + try { + double price = Double.parseDouble(priceString); + if (price < 0) { + throw new Exception(); + } + return true; + } catch (Exception e) { + ui.print("Invalid price provided!"); + } + return false; + } + + /** + * Checks if a medicine expiry date is valid. + * + * @param ui Reference to the UI object to print messages. + * @param expiryString Expiry date of the medicine. + * @return Boolean value indicating if medicine expiry date is valid. + */ + public boolean isValidExpiry(Ui ui, String expiryString) { + try { + DateParser.stringToDate(expiryString); + return true; + } catch (Exception e) { + ui.print("Invalid expiry date! Ensure date is in " + DateParser.OUTPUT_DATE_FORMAT + "."); + } + return false; + } + + /** + * Checks if a medicine description is empty. + * + * @param ui Reference to the UI object to print messages. + * @param description Medicine description to be checked. + * @return Boolean value indicating if medicine name is valid. + */ + public boolean isValidDescription(Ui ui, String description) { + if (description.equals("")) { + ui.print("Description cannot be empty!"); + return false; + } + return true; + } + + /** + * Checks if a medicine max quantity is valid. + * + * @param ui Reference to the UI object to print messages. + * @param maxQuantityString Max quantity of the medicine. + * @return Boolean value indicating if max medicine quantity is valid. + */ + public boolean isValidMaxQuantity(Ui ui, String maxQuantityString) { + try { + int maxQuantity = Integer.parseInt(maxQuantityString); + if (maxQuantity < 0) { + throw new Exception(); + } + return true; + } catch (Exception e) { + ui.print("Invalid max quantity provided!"); + } + return false; + } + + /** + * Checks if a medicine column/alias exists. + * + * @param ui Reference to the UI object to print messages. + * @param columnName Column name/alias to be validated. + * @return Boolean value indicating if column name is value. + */ + public boolean isValidColumn(Ui ui, String columnName) { + String[] columnAlias = new String[]{CommandParameters.ID, CommandParameters.NAME, CommandParameters.PRICE, + CommandParameters.QUANTITY, CommandParameters.EXPIRY_DATE, CommandParameters.DESCRIPTION, + CommandParameters.MAX_QUANTITY}; + if (Arrays.asList(Stock.COLUMNS).contains(columnName.toUpperCase()) || Arrays.asList(columnAlias) + .contains(columnName.toLowerCase())) { + return true; + } + ui.print("Invalid column name/alias! Column names can only be " + Arrays.toString(Stock.COLUMNS) + " and " + + "the respective aliases are " + Arrays.toString(columnAlias) + "."); + return false; + } + + /** + * Checks if total quantity of medicine is below max quantity. + * + * @param ui Reference to the UI object to print messages. + * @param quantity Quantity of the medicines. + * @param maxQuantity Max quantity of medicines. + * @return Boolean value indicating if total quantity is less than max quantity. + */ + public boolean quantityValidityChecker(Ui ui, int quantity, int maxQuantity) { + if (quantity > maxQuantity) { + String message = "Quantity: " + quantity + ", Max Quantity: " + maxQuantity; + ui.print("Quantity cannot be more than maximum quantity!"); + ui.print(message); + return false; + } + return true; + } + + /** + * Checks if input date for medicine already exists. + * + * @param ui Reference to the UI object to print messages. + * @param medicines List of all medicines. + * @param expiryDate Expiry date given by user + * @param name Medicine name to check against + * @return Boolean false if same expiry date exist + */ + public boolean dateValidityChecker(Ui ui, ArrayList medicines, Date expiryDate, String name) { + ArrayList filteredStocks = new ArrayList<>(); + for (Medicine medicine : medicines) { + if (!(medicine instanceof Stock)) { + continue; + } + Stock stock = (Stock) medicine; + boolean isSameName = name.equalsIgnoreCase(stock.getMedicineName()); + boolean isDeleted = stock.isDeleted(); + if (isSameName && !isDeleted) { + filteredStocks.add(stock); + } + } + + Date currentDate = new Date(); + String currentDateString = DateParser.dateToString(currentDate); + + try { + currentDate = DateParser.stringToDate(currentDateString); + } catch (ParseException e) { + e.printStackTrace(); + } + + if (expiryDate.before(currentDate)) { + ui.print("Expired date detected! Action aborted."); + return false; + } + + for (Stock filteredStock : filteredStocks) { + if (expiryDate.equals(filteredStock.getExpiry())) { + ui.print("Same expiry date already exists! Action aborted."); + return false; + } + } + return true; + } +} diff --git a/src/main/java/utilities/storage/Storage.java b/src/main/java/utilities/storage/Storage.java new file mode 100644 index 0000000000..d07a65545d --- /dev/null +++ b/src/main/java/utilities/storage/Storage.java @@ -0,0 +1,337 @@ +package utilities.storage; + +import errors.InvalidDataException; +import inventory.Prescription; +import inventory.Medicine; +import inventory.Order; +import utilities.parser.FileParser; +import inventory.Stock; +import utilities.ui.Ui; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Scanner; + +//@@author RemusTeo + +/** + * Storage class handles all saving and loading of data. + */ +public class Storage { + private static final String DIRECTORY_PATH = "data"; + private static final String STOCK_FILE_PATH = "data/stock.txt"; + private static final String ORDER_FILE_PATH = "data/order.txt"; + private static final String PRESCRIPTION_FILE_PATH = "data/prescription.txt"; + private static final String ORDER_ARCHIVE_FILE_PATH = "data/order_archive.txt"; + private static final String PRESCRIPTION_ARCHIVE_FILE_PATH = "data/prescription_archive.txt"; + private static final String DELIMITER = "\\|"; + private static final int NUMBER_OF_STOCK_DATA_FIELDS = 8; + private static final int NUMBER_OF_ORDER_DATA_FIELDS = 5; + private static final int NUMBER_OF_PRESCRIPTION_DATA_FIELDS = 7; + private static File stockFile; + private static File orderFile; + private static File prescriptionFile; + private static File orderArchiveFile; + private static File prescriptionArchiveFile; + private static Storage storage = null; + int highestStockId = 0; + int highestOrderId = 0; + int highestPrescriptionId = 0; + + + /** + * Helps to create the Storage instance or returns the Storage instance if it exists. + * + * @return The Storage instance. + */ + public static Storage getInstance() { + if (storage == null) { + storage = new Storage(); + } + return storage; + } + + /** + * Constructor of Storage class. + * Initializes all files objects. + */ + public Storage() { + stockFile = new File(STOCK_FILE_PATH); + orderFile = new File(ORDER_FILE_PATH); + prescriptionFile = new File(PRESCRIPTION_FILE_PATH); + orderArchiveFile = new File(ORDER_ARCHIVE_FILE_PATH); + prescriptionArchiveFile = new File(PRESCRIPTION_ARCHIVE_FILE_PATH); + } + + /** + * Save data into specific files after categorising them into Stock, Order and Prescription. + */ + public void saveData(ArrayList medicines) { + String stockData = ""; + String orderData = ""; + String prescriptionData = ""; + for (Medicine medicine : medicines) { + String data = medicine.toFileFormat() + System.lineSeparator(); + if (medicine instanceof Stock) { + stockData += data; + } else if (medicine instanceof Order) { + orderData += data; + } else if (medicine instanceof Prescription) { + prescriptionData += data; + } + } + + File dataDirectory = new File(DIRECTORY_PATH); + if (!dataDirectory.exists()) { + dataDirectory.mkdir(); + } + + try { + stockFile.createNewFile(); + writeToFile(stockData, STOCK_FILE_PATH); + orderFile.createNewFile(); + writeToFile(orderData, ORDER_FILE_PATH); + prescriptionFile.createNewFile(); + writeToFile(prescriptionData, PRESCRIPTION_FILE_PATH); + } catch (IOException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + + /** + * Archive data into specific files after categorising them into Order and Prescription. + */ + public void archiveData(ArrayList medicines) { + String orderArchiveData = ""; + String prescriptionData = ""; + for (Medicine medicine : medicines) { + String data = medicine.toArchiveFormat() + System.lineSeparator(); + if (medicine instanceof Stock) { + continue; // No archive for stock + } else if (medicine instanceof Order) { + orderArchiveData += data; + } else if (medicine instanceof Prescription) { + prescriptionData += data; + } + } + + File dataDirectory = new File(DIRECTORY_PATH); + if (!dataDirectory.exists()) { + dataDirectory.mkdir(); + } + + try { + orderArchiveFile.createNewFile(); + appendToFile(orderArchiveData, ORDER_ARCHIVE_FILE_PATH); + prescriptionArchiveFile.createNewFile(); + appendToFile(prescriptionData, PRESCRIPTION_ARCHIVE_FILE_PATH); + } catch (IOException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + + /** + * Write data into file corresponding files. + * + * @param data Data to be written into the file. + * @throws IOException If unable to write into file. + */ + private void writeToFile(String data, String filePath) throws IOException { + FileWriter fw = new FileWriter(filePath); + fw.write(data); + fw.close(); + } + + /** + * Append data into file corresponding files. + * + * @param data Data to be appended into the file. + * @throws IOException If unable to append into file. + */ + private void appendToFile(String data, String filePath) throws IOException { + FileWriter fw = new FileWriter(filePath, true); + fw.write(data); + fw.close(); + } + + /** + * Load saved data from data/stock.txt, data/order.txt, data/prescription.txt. + */ + public ArrayList loadData() { + ArrayList medicines = new ArrayList<>(); + if (stockFile.exists()) { + try { + medicines.addAll(readFromFile("STOCK", stockFile)); + } catch (FileNotFoundException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + if (orderFile.exists()) { + try { + medicines.addAll(readFromFile("ORDER", orderFile)); + } catch (FileNotFoundException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + if (prescriptionFile.exists()) { + try { + medicines.addAll(readFromFile("PRESCRIPTION", prescriptionFile)); + } catch (FileNotFoundException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + return medicines; + } + + /** + * Read and process medicine details from corresponding file to restore medicine state in program. + * + * @param file File object of data/stock.txt, data/order.txt, or data/prescription.txt + * @throws FileNotFoundException If file is not found. + */ + private ArrayList readFromFile(String fileType, File file) throws FileNotFoundException { + Scanner sc = new Scanner(file); + ArrayList medicines = new ArrayList<>(); + int stockRow = 1; + int orderRow = 1; + int prescriptionRow = 1; + while (sc.hasNextLine()) { + String medicineDetails = sc.nextLine(); + try { + switch (fileType) { + case "STOCK": + Medicine parsedStock = parseStockData(medicineDetails, stockRow); + medicines.add(parsedStock); + stockRow++; + break; + case "ORDER": + Medicine parsedOrder = parseOrderData(medicineDetails, orderRow); + medicines.add(parsedOrder); + orderRow++; + break; + case "PRESCRIPTION": + Medicine parsedPrescription = parsePrescriptionData(medicineDetails, prescriptionRow); + medicines.add(parsedPrescription); + prescriptionRow++; + break; + default: + break; + } + } catch (InvalidDataException e) { + Ui ui = Ui.getInstance(); + ui.print(e.getMessage()); + ui.print("Corrupted data detected in file. Please fix error in file. Quitting MediVault now."); + System.exit(0); + } + } + return medicines; + } + + /** + * Parse stock data and create a stock object based on it. + * + * @param stockDetails String of data of specific stock from file data/stock.txt. + * @return Stock object for adding into medicines. + */ + private Medicine parseStockData(String stockDetails, int stockRow) throws InvalidDataException { + String[] splitStockDetails = stockDetails.split(DELIMITER); + if (splitStockDetails.length != NUMBER_OF_STOCK_DATA_FIELDS) { + throw new InvalidDataException("[ROW: " + stockRow + "] INVALID NUMBER OF DELIMITER OR FIELDS" + + " [data/stock.txt]"); + } + int stockId = FileParser.parseStockId(splitStockDetails, stockRow); + String stockName = FileParser.parseStockName(splitStockDetails, stockRow); + double stockPrice = FileParser.parseStockPrice(splitStockDetails, stockRow); + int stockQuantity = FileParser.parseStockQuantity(splitStockDetails, stockRow); + Date stockExpiry = FileParser.parseStockExpiry(splitStockDetails, stockRow); + String stockDescription = FileParser.parseStockDescription(splitStockDetails, stockRow); + int stockMaxQuantity = FileParser.parseStockMaxQuantity(splitStockDetails, stockRow); + boolean stockIsDeleted = FileParser.parseStockIsDeleted(splitStockDetails, stockRow); + + Stock stock = new Stock(stockName, stockPrice, stockQuantity, stockExpiry, stockDescription, stockMaxQuantity); + if (stockId > highestStockId) { + stock.setStockId(stockId); + stock.setStockCount(stockId); + highestStockId = stockId; + } else { + stock.setStockId(stockId); + stock.setStockCount(highestStockId); + } + stock.setDeleted(stockIsDeleted); + return stock; + } + + /** + * Parse order data and create an order object based on it. + * + * @param orderDetails String of data of specific order from file data/order.txt. + * @return Order object for adding into medicines. + */ + private Medicine parseOrderData(String orderDetails, int orderRow) throws InvalidDataException { + String[] orderStockDetails = orderDetails.split(DELIMITER); + if (orderStockDetails.length != NUMBER_OF_ORDER_DATA_FIELDS) { + throw new InvalidDataException("[ROW: " + orderRow + "] INVALID NUMBER OF DELIMITER OR FIELDS" + + " [data/order.txt]"); + } + int orderId = FileParser.parseOrderId(orderStockDetails, orderRow); + String orderName = FileParser.parseOrderName(orderStockDetails, orderRow); + int orderQuantity = FileParser.parseOrderQuantity(orderStockDetails, orderRow); + Date orderDate = FileParser.parseOrderDate(orderStockDetails, orderRow); + boolean orderStatus = FileParser.parseOrderStatus(orderStockDetails, orderRow); + + Order order = new Order(orderName, orderQuantity, orderDate); + if (orderStatus) { + order.setDelivered(); + } + if (orderId > highestOrderId) { + order.setOrderId(orderId); + order.setOrderCount(orderId); + highestOrderId = orderId; + } else { + order.setOrderId(orderId); + order.setOrderCount(highestOrderId); + } + return order; + } + + /** + * Parse prescription data and create a prescription object based on it. + * + * @param prescriptionDetails String of data of specific stock from file data/prescription.txt. + * @param prescriptionRow Row ID of the prescription. + * @return Prescription object for adding into medicines. + */ + private Medicine parsePrescriptionData(String prescriptionDetails, int prescriptionRow) + throws InvalidDataException { + String[] splitPrescriptionDetails = prescriptionDetails.split(DELIMITER); + if (splitPrescriptionDetails.length != NUMBER_OF_PRESCRIPTION_DATA_FIELDS) { + throw new InvalidDataException("[ROW: " + prescriptionRow + "] INVALID NUMBER OF DELIMITER OR FIELDS" + + " [data/prescription.txt]"); + } + int prescriptionId = FileParser.parsePrescriptionId(splitPrescriptionDetails, prescriptionRow); + String prescriptionName = FileParser.parsePrescriptionName(splitPrescriptionDetails, prescriptionRow); + int prescriptionQuantity = FileParser.parsePrescriptionQuantity(splitPrescriptionDetails, prescriptionRow); + String prescriptionCustomerId = FileParser.parsePrescriptionCustomerId(splitPrescriptionDetails, + prescriptionRow); + Date prescriptionDate = FileParser.parsePrescriptionDate(splitPrescriptionDetails, prescriptionRow); + String prescriptionStaff = FileParser.parsePrescriptionStaff(splitPrescriptionDetails, prescriptionRow); + int prescriptionStockId = FileParser.parsePrescriptionStockId(splitPrescriptionDetails, prescriptionRow); + + Prescription prescription = new Prescription(prescriptionName, prescriptionQuantity, prescriptionCustomerId, + prescriptionDate, prescriptionStaff, prescriptionStockId); + if (prescriptionId > highestPrescriptionId) { + prescription.setPrescriptionId(prescriptionId); + prescription.setPrescriptionCount(prescriptionId); + highestPrescriptionId = prescriptionId; + } else { + prescription.setPrescriptionId(prescriptionId); + prescription.setPrescriptionCount(highestPrescriptionId); + } + return prescription; + } +} diff --git a/src/main/java/utilities/ui/Ui.java b/src/main/java/utilities/ui/Ui.java new file mode 100644 index 0000000000..0e12cb0e4b --- /dev/null +++ b/src/main/java/utilities/ui/Ui.java @@ -0,0 +1,505 @@ +package utilities.ui; + +import command.CommandSyntax; +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.parser.OrderManager; + +import java.util.ArrayList; +import java.util.Scanner; + +//@@author alvintan01 + +/** + * Handles printing all messages in the application to the console. + */ +public class Ui { + private static final int TABLE_PADDING = 2; + private static final int DESCRIPTION_MAX_WIDTH = 40; + private static final int HELP_MAX_WIDTH = 50; + private static Ui ui = null; + private static Scanner scanner; + + /** + * Helps to create the UI instance or returns the UI instance if it exists. + * + * @return The Ui instance. + */ + public static Ui getInstance() { + if (ui == null) { + ui = new Ui(); + } + return ui; + } + + /** + * Helps to create the Scanner instance and returns the user input. + * + * @return The input given by user. + */ + public String getInput() { + if (scanner == null) { + scanner = new Scanner(System.in); + } + return scanner.nextLine(); + } + + /** + * Prints the welcome message and logo. + */ + public void printWelcomeMessage() { + String logo = "| \\/ | | |(_)| | | | | || | \n" + + "| . . | ___ __| | _ | | | | __ _ _ _ | || |_ \n" + + "| |\\/| | / _ \\ / _` || || | | | / _` || | | || || __|\n" + + "| | | || __/| (_| || |\\ \\_/ /| (_| || |_| || || |_ \n" + + "\\_| |_/ \\___| \\__,_||_| \\___/ \\__,_| \\__,_||_| \\__|\n"; + print(logo + "Welcome to MediVault!"); + } + + /** + * Prints the invalid command message. + */ + public void printInvalidCommandMessage() { + print("Sorry! I did not understand your command."); + } + + /** + * Prints the statements. + * + * @param statement Statement to be printed. + */ + public void print(String statement) { + System.out.println(statement); + } + + /** + * Prints the invalid parameter message. + * + * @param parameter The invalid parameter found. + * @param commandSyntax The command syntax of the command. + */ + public void printInvalidParameter(String parameter, String commandSyntax) { + if (parameter.equals("")) { + print("Please provide all the required parameters!"); + } else { + print("An invalid parameter " + parameter + " is provided!"); + } + printCommandSyntax(commandSyntax); + } + + /** + * Prints the missing parameters message. + * + * @param parameters The parameters that are not found. + * @param commandSyntax The command syntax of the command. + */ + public void printRequiredParameters(ArrayList parameters, String commandSyntax) { + if (parameters.size() == 1) { + print("Required parameter " + parameters.toString() + " is missing!"); + } else { + print("Required parameters " + parameters.toString() + " are missing!"); + } + printCommandSyntax(commandSyntax); + } + + /** + * Prints the command syntax. + * + * @param commandSyntax The command syntax of the command. + */ + public void printCommandSyntax(String commandSyntax) { + print("COMMAND SYNTAX: " + commandSyntax); + } + + /** + * Prints out the medicine in a table format. + * + * @param stock Stock to be printed. + */ + public void printStock(Stock stock) { + ArrayList stocks = new ArrayList<>(); + ArrayList medicines = new ArrayList<>(); + stocks.add(stock); + printStocks(stocks, medicines); + } + + /** + * Prints out all the stocks in the Arraylist in a table format. + * + * @param stocks Arraylist of the stocks. + * @param medicines Arraylist of the medicines. + */ + public void printStocks(ArrayList stocks, ArrayList medicines) { + if (stocks.size() == 0) { + print("There are no stocks found."); + return; + } + int idWidth = Stock.COLUMNS[0].length(); + int nameWidth = Stock.COLUMNS[1].length(); + int priceWidth = Stock.COLUMNS[2].length(); + int quantityWidth = Stock.COLUMNS[3].length(); + int expiryWidth = Stock.COLUMNS[4].length(); + int descriptionWidth = Stock.COLUMNS[5].length(); + int maxQuantityWidth = Stock.COLUMNS[6].length(); + + // Find the longest width of each column + for (Stock stock : stocks) { + idWidth = Math.max(String.valueOf(stock.getStockId()).length(), idWidth); + nameWidth = Math.max(stock.getMedicineName().length(), nameWidth); + priceWidth = Math.max(String.format("$%.2f", stock.getPrice()).length(), priceWidth); + quantityWidth = Math.max(String.valueOf(stock.getQuantity()).length(), quantityWidth); + int orderQuantity = OrderManager.getTotalOrderQuantity(medicines, stock.getMedicineName()); + if (orderQuantity != 0) { + quantityWidth = Math.max(("PENDING: " + orderQuantity).length(), quantityWidth); + } + expiryWidth = Math.max(DateParser.dateToString(stock.getExpiry()).length(), expiryWidth); + descriptionWidth = Math.max(stock.getDescription().length(), descriptionWidth); + maxQuantityWidth = Math.max(String.valueOf(stock.getMaxQuantity()).length(), maxQuantityWidth); + } + descriptionWidth = Math.min(descriptionWidth, DESCRIPTION_MAX_WIDTH); + + int[] columnWidths = {idWidth, nameWidth, priceWidth, quantityWidth, expiryWidth, descriptionWidth, + maxQuantityWidth}; + + // Pad the data in the columns + String idFormat = "| %1$-" + idWidth + "s | "; + String nameFormat = "%1$-" + nameWidth + "s | "; + String priceFormat = "%1$-" + priceWidth + "s | "; + String quantityFormat = "%1$-" + quantityWidth + "s | "; + String expiryFormat = "%1$-" + expiryWidth + "s | "; + String descriptionFormat = "%1$-" + descriptionWidth + "s | "; + String maxQuantityFormat = "%1$-" + maxQuantityWidth + "s | "; + + String[] formats = {idFormat, nameFormat, priceFormat, quantityFormat, expiryFormat, descriptionFormat, + maxQuantityFormat}; + StringBuilder headers = new StringBuilder(); + for (int i = 0; i < columnWidths.length; i++) { + headers.append(String.format(formats[i], centerString(columnWidths[i], Stock.COLUMNS[i]))); + } + + printHeaderBorder(columnWidths); + System.out.println(headers); + printHeaderBorder(columnWidths); + + for (Stock stock : stocks) { + String description = stock.getDescription(); + String truncatedDescription = truncateDescription(description, 0, DESCRIPTION_MAX_WIDTH); + int orderQuantity = OrderManager.getTotalOrderQuantity(medicines, stock.getMedicineName()); + int descriptionIndex = truncatedDescription.length(); + + String row = String.format(idFormat, centerString(idWidth, String.valueOf(stock.getStockId()))) + + String.format(nameFormat, centerString(nameWidth, stock.getMedicineName())) + + String.format(priceFormat, centerString(priceWidth, String.format("$%.2f", stock.getPrice()))) + + String.format(quantityFormat, centerString(quantityWidth, String.valueOf(stock.getQuantity()))) + + String.format(expiryFormat, centerString(expiryWidth, + DateParser.dateToString(stock.getExpiry()))) + + String.format(descriptionFormat, centerString(descriptionWidth, truncatedDescription)) + + String.format(maxQuantityFormat, centerString(maxQuantityWidth, + String.valueOf(stock.getMaxQuantity()))); + + while (descriptionIndex < description.length() || orderQuantity != 0) { + truncatedDescription = truncateDescription(description, descriptionIndex, DESCRIPTION_MAX_WIDTH); + descriptionIndex += truncatedDescription.length(); + + row += "\n" + String.format(idFormat, "") + String.format(nameFormat, "") + + String.format(priceFormat, "") + + String.format(quantityFormat, (orderQuantity == 0) ? "" : ("PENDING: " + orderQuantity)) + + String.format(expiryFormat, "") + + String.format(descriptionFormat, centerString(descriptionWidth, truncatedDescription)) + + String.format(maxQuantityFormat, ""); + orderQuantity = 0; // Reset the quantity count to prevent looping + } + System.out.println(row.toUpperCase()); + printRowBorder(columnWidths); + } + } + + /** + * Helps to truncate the description of a table column and ensure that it does not truncate a word in the middle. + * It will ensure that the truncated description returned contains valid words. + * + * @param description Description value in the column. + * @param startingIndex The starting index to truncate the description. + * @param maxWidth The maximum number of characters allowed per row. + */ + private String truncateDescription(String description, int startingIndex, int maxWidth) { + String truncatedDescription = ""; + int descriptionIndex = Math.min(description.length(), startingIndex + maxWidth); + truncatedDescription = description.substring(startingIndex, descriptionIndex); + + String[] descriptionSplit = truncatedDescription.split("\\s"); // Split by spaces + // Ensure that last word is not a space + if (descriptionIndex < description.length()) { + if (description.charAt(descriptionIndex) != ' ' && descriptionSplit.length > 1) { + descriptionSplit[descriptionSplit.length - 1] = ""; // Remove the last partial word + } + truncatedDescription = String.join(" ", descriptionSplit); + } + return truncatedDescription; + } + + /** + * Prints the header borders for a table. + * + * @param columnWidths Array of the columns widths in the table. + */ + private void printHeaderBorder(int[] columnWidths) { + StringBuilder headerBorder = new StringBuilder(); + for (int columnWidth : columnWidths) { + headerBorder.append("+"); + int width = columnWidth + TABLE_PADDING; + headerBorder.append("=".repeat(width)); + } + headerBorder.append("+"); + System.out.println(headerBorder); + } + + /** + * Prints the row borders for a table. + * + * @param columnWidths Array of the columns widths in the table. + */ + private void printRowBorder(int[] columnWidths) { + StringBuilder rowBorder = new StringBuilder(); + for (int columnWidth : columnWidths) { + rowBorder.append("+"); + int width = columnWidth + TABLE_PADDING; + rowBorder.append("-".repeat(width)); + } + rowBorder.append("+"); + System.out.println(rowBorder); + } + + /** + * Centralise a string given a width. + * + * @param width Width of the column. + * @param s String to be centralised. + * @return The centralised string. + */ + private String centerString(int width, String s) { + int middleIndex = (s.length() + (width - s.length()) / 2); + return String.format("%-" + width + "s", String.format("%" + middleIndex + "s", s)); + } + + /** + * Prints out help table with the accepted commands and their respective syntaxes. + * + * @param commandSyntaxes Arraylist of the commandSyntax to be printed. + */ + public void printHelpMessage(ArrayList commandSyntaxes) { + int commandWidth = CommandSyntax.COMMAND.length(); + int commandSyntaxWidth = CommandSyntax.COMMAND_SYNTAX.length(); + for (CommandSyntax commandSyntax : commandSyntaxes) { + commandWidth = Math.max(commandWidth, commandSyntax.getCommandName().length()); + commandSyntaxWidth = Math.max(commandSyntaxWidth, commandSyntax.getCommandSyntax().length()); + } + commandSyntaxWidth = Math.min(commandSyntaxWidth, HELP_MAX_WIDTH); + int[] columnWidths = {commandWidth, commandSyntaxWidth}; + + String commandFormat = "| %1$-" + commandWidth + "s | "; + String commandSyntaxFormat = "%1$-" + commandSyntaxWidth + "s | "; + String[] formats = {commandFormat, commandSyntaxFormat}; + + StringBuilder headers = new StringBuilder(); + for (int i = 0; i < CommandSyntax.NO_OF_COLUMNS; i++) { + headers.append(String.format(formats[i], centerString(columnWidths[i], CommandSyntax.COLUMNS[i]))); + } + System.out.println("Welcome to the help page."); + System.out.println("Your current mode is indicated in the square brackets at the bottom left of the console."); + System.out.println("It allows you to type add, list, update, delete without typing in the full command."); + System.out.println("Additionally, you can type archive in both prescription and order mode and receive in order" + + " mode."); + System.out.println("Type stock, prescription or order to change to respective modes."); + System.out.println("Note that parameters in {curly braces} are optional."); + System.out.println("Parameters in [square braces] indicate that at least one of the parameter(s) must be " + + "provided."); + System.out.println("Parameters enclosed in (round brackets) are conditional optional parameters."); + System.out.println("For example, the parameters d/DESCRIPTION and m/MAX_QUANTITY in addstock and receiveorder " + + "will be optional only if the stock exists."); + printHeaderBorder(columnWidths); + System.out.println(headers); + printHeaderBorder(columnWidths); + + for (CommandSyntax commandSyntax : commandSyntaxes) { + String commandSyntaxString = commandSyntax.getCommandSyntax(); + int currentIndex = 0; + String truncatedCommandSyntax = truncateDescription(commandSyntaxString, currentIndex, HELP_MAX_WIDTH); + currentIndex += truncatedCommandSyntax.length(); + String row = String.format(commandFormat, centerString(commandWidth, commandSyntax.getCommandName())) + + String.format(commandSyntaxFormat, truncatedCommandSyntax); + System.out.println(row); + // Truncate the help command syntax + while (currentIndex < commandSyntaxString.length()) { + truncatedCommandSyntax = truncateDescription(commandSyntaxString, currentIndex, HELP_MAX_WIDTH); + row = String.format(commandFormat, centerString(commandWidth, "")) + + String.format(commandSyntaxFormat, truncatedCommandSyntax); + System.out.println(row); + currentIndex += truncatedCommandSyntax.length(); + } + + printRowBorder(columnWidths); + } + System.out.println("For more information, refer to User Guide: https://ay2122s1-cs2113t-t10-1.github.io/tp/"); + } + + /** + * Prints out the order in a table format. + * + * @param order Order to be printed. + */ + public void printOrder(Order order) { + ArrayList orders = new ArrayList<>(); + orders.add(order); + printOrders(orders); + } + + /** + * Prints out all the orders in the Arraylist in a table format. + * + * @param orders Arraylist of the orders. + */ + public void printOrders(ArrayList orders) { + if (orders.size() == 0) { + print("There are no orders found."); + return; + } + + int idWidth = Order.COLUMNS[0].length(); + int nameWidth = Order.COLUMNS[1].length(); + int quantityWidth = Order.COLUMNS[2].length(); + int dateWidth = Order.COLUMNS[3].length(); + int statusWidth = Order.COLUMNS[4].length(); + + // Find the longest width of each column + for (Order order : orders) { + idWidth = Math.max(String.valueOf(order.getOrderId()).length(), idWidth); + nameWidth = Math.max(order.getMedicineName().length(), nameWidth); + quantityWidth = Math.max(String.valueOf(order.getQuantity()).length(), quantityWidth); + dateWidth = Math.max(DateParser.dateToString(order.getDate()).length(), dateWidth); + statusWidth = Math.max(order.getStatus().length(), statusWidth); + } + + int[] columnWidths = {idWidth, nameWidth, quantityWidth, dateWidth, statusWidth}; + + // Pad the data in the columns + String idFormat = "| %1$-" + idWidth + "s | "; + String nameFormat = "%1$-" + nameWidth + "s | "; + String quantityFormat = "%1$-" + quantityWidth + "s | "; + String dateFormat = "%1$-" + dateWidth + "s | "; + String statusFormat = "%1$-" + statusWidth + "s | "; + + String[] formats = {idFormat, nameFormat, quantityFormat, dateFormat, statusFormat}; + + StringBuilder headers = new StringBuilder(); + for (int i = 0; i < columnWidths.length; i++) { + headers.append(String.format(formats[i], centerString(columnWidths[i], Order.COLUMNS[i]))); + } + + printHeaderBorder(columnWidths); + System.out.println(headers); + printHeaderBorder(columnWidths); + + for (Order order : orders) { + String row = String.format(idFormat, centerString(idWidth, String.valueOf(order.getOrderId()))) + + String.format(nameFormat, centerString(nameWidth, order.getMedicineName())) + + String.format(quantityFormat, centerString(quantityWidth, String.valueOf(order.getQuantity()))) + + String.format(dateFormat, centerString(dateWidth, DateParser.dateToString(order.getDate()))) + + String.format(statusFormat, centerString(statusWidth, String.valueOf(order.getStatus()))); + System.out.println(row.toUpperCase()); + printRowBorder(columnWidths); + } + } + + /** + * Prints out the prescription in a table format. + * + * @param prescription Prescription to be printed. + */ + public void printPrescription(Prescription prescription) { + ArrayList prescriptions = new ArrayList<>(); + prescriptions.add(prescription); + printPrescriptions(prescriptions); + } + + /** + * Prints out all the medicines prescribed in a table format. + * + * @param prescriptions Arraylist of the prescription. + */ + public void printPrescriptions(ArrayList prescriptions) { + if (prescriptions.size() == 0) { + print("There are no records of medicines prescribed."); + return; + } + + int idWidth = Prescription.COLUMNS[0].length(); + int nameWidth = Prescription.COLUMNS[1].length(); + int quantityWidth = Prescription.COLUMNS[2].length(); + int customerIdWidth = Prescription.COLUMNS[3].length(); + int dateWidth = Prescription.COLUMNS[4].length(); + int staffWidth = Prescription.COLUMNS[5].length(); + int stockIdWidth = Prescription.COLUMNS[6].length(); + + // Find the longest width of each column + for (Prescription prescription : prescriptions) { + idWidth = Math.max(String.valueOf(prescription.getPrescriptionId()).length(), idWidth); + nameWidth = Math.max(prescription.getMedicineName().length(), nameWidth); + quantityWidth = Math.max(String.valueOf(prescription.getQuantity()).length(), quantityWidth); + customerIdWidth = Math.max(prescription.getCustomerId().length(), customerIdWidth); + dateWidth = Math.max(DateParser.dateToString(prescription.getDate()).length(), dateWidth); + staffWidth = Math.max(prescription.getStaff().length(), staffWidth); + stockIdWidth = Math.max(String.valueOf(prescription.getStockId()).length(), stockIdWidth); + } + + int[] columnWidths = {idWidth, nameWidth, quantityWidth, customerIdWidth, dateWidth, staffWidth, stockIdWidth}; + + // Pad the data in the columns + String idFormat = "| %1$-" + idWidth + "s | "; + String nameFormat = "%1$-" + nameWidth + "s | "; + String quantityFormat = "%1$-" + quantityWidth + "s | "; + String customerIdFormat = "%1$-" + customerIdWidth + "s | "; + String dateFormat = "%1$-" + dateWidth + "s | "; + String staffFormat = "%1$-" + staffWidth + "s | "; + String stockIdFormat = "%1$-" + stockIdWidth + "s | "; + + String[] formats = {idFormat, nameFormat, quantityFormat, customerIdFormat, dateFormat, staffFormat, + stockIdFormat}; + + StringBuilder headers = new StringBuilder(); + for (int i = 0; i < columnWidths.length; i++) { + headers.append(String.format(formats[i], centerString(columnWidths[i], Prescription.COLUMNS[i]))); + } + + printHeaderBorder(columnWidths); + System.out.println(headers); + printHeaderBorder(columnWidths); + + for (Prescription prescription : prescriptions) { + String row = String.format(idFormat, centerString(idWidth, + String.valueOf(prescription.getPrescriptionId()))) + + String.format(nameFormat, centerString(nameWidth, prescription.getMedicineName())) + + String.format(quantityFormat, centerString(quantityWidth, + String.valueOf(prescription.getQuantity()))) + + String.format(customerIdFormat, centerString(customerIdWidth, + String.valueOf(prescription.getCustomerId()))) + + String.format(dateFormat, centerString(dateWidth, + DateParser.dateToString(prescription.getDate()))) + + String.format(staffFormat, centerString(staffWidth, prescription.getStaff())) + + String.format(stockIdFormat, centerString(stockIdWidth, + String.valueOf(prescription.getStockId()))); + System.out.println(row.toUpperCase()); + printRowBorder(columnWidths); + } + } + + /** + * Prints the exit message. + */ + public void printExit() { + print("Quitting Medivault..."); + } +} diff --git a/src/test/java/HelpCommandTest.java b/src/test/java/HelpCommandTest.java new file mode 100644 index 0000000000..6c2450fac2 --- /dev/null +++ b/src/test/java/HelpCommandTest.java @@ -0,0 +1,97 @@ +import command.HelpCommand; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author alvintan01 + +public class HelpCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @Test + public void helpCommand_noParameters_expectHelpTable() { + String expectedOutput = "Welcome to the help page.\n" + + "Your current mode is indicated in the square brackets at the bottom left of the console.\n" + + "It allows you to type add, list, update, delete without typing in the full command.\n" + + "Additionally, you can type archive in both prescription and order mode and receive in order mode.\n" + + "Type stock, prescription or order to change to respective modes.\n" + + "Note that parameters in {curly braces} are optional.\n" + + "Parameters in [square braces] indicate that at least one of the parameter(s) must be provided.\n" + + "Parameters enclosed in (round brackets) are conditional optional parameters.\n" + + "For example, the parameters d/DESCRIPTION and m/MAX_QUANTITY in addstock and receiveorder will be " + + "optional only if the stock exists.\n" + + "+=====================+====================================================+\n" + + "| COMMAND | COMMAND SYNTAX | \n" + + "+=====================+====================================================+\n" + + "| addstock | addstock n/NAME p/PRICE q/QUANTITY e/EXPIRY_DATE | \n" + + "| | (d/DESCRIPTION m/MAX_QUANTITY) | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| deletestock | deletestock [i/ID expiring/EXPIRY_DATE] | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| updatestock | updatestock i/ID [n/NAME p/PRICE q/QUANTITY | \n" + + "| | e/EXPIRY_DATE d/DESCRIPTION m/MAX_QUANTITY] | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| liststock | liststock {i/ID n/NAME p/PRICE | \n" + + "| | q/QUANTITYlow/LESS_THAN_OR_EQUAL_QUANTITY | \n" + + "| | e/EXPIRY_DATE | \n" + + "| | expiring/LESS_THAN_OR_EQUAL_EXPIRY_DATE | \n" + + "| | d/DESCRIPTION m/MAX_QUANTITY sort/COLUMN_NAME | \n" + + "| | rsort/COLUMN_NAME} | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| addprescription | addprescription n/NAME q/QUANTITY c/CUSTOMER_ID | \n" + + "| | s/STAFF_NAME | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| deleteprescription | deleteprescription i/ID | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| updateprescription | updateprescription i/ID [n/NAME q/QUANTITY | \n" + + "| | c/CUSTOMER_ID d/DATE s/STAFF_NAME] | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| listprescription | listprescription {i/ID n/NAME q/QUANTITY | \n" + + "| | c/CUSTOMER_ID d/DATE s/STAFF_NAME sid/STOCK_ID | \n" + + "| | sort/COLUMN_NAME rsort/COLUMN_NAME} | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| archiveprescription | archiveprescription d/DATE | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| addorder | addorder n/NAME q/QUANTITY {d/DATE} | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| deleteorder | deleteorder i/ID | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| updateorder | updateorder i/ID [n/NAME q/QUANTITY d/DATE] | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| listorder | listorder {i/ID n/NAME q/QUANTITY d/DATE | \n" + + "| | s/STATUS sort/COLUMN_NAME rsort/COLUMN_NAME} | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| archiveorder | archiveorder d/DATE | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| receiveorder | receiveorder i/ID p/PRICE e/EXPIRY_DATE | \n" + + "| | (d/DESCRIPTION m/MAX_QUANTITY) | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| purge | purge | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| help | help | \n" + + "+---------------------+----------------------------------------------------+\n" + + "| exit | exit | \n" + + "+---------------------+----------------------------------------------------+\n" + + "For more information, refer to User Guide: https://ay2122s1-cs2113t-t10-1.github.io/tp/"; + + new HelpCommand().execute(); + // Output stream will include \r for each line break + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/PurgeCommandTest.java b/src/test/java/PurgeCommandTest.java new file mode 100644 index 0000000000..aa589897f9 --- /dev/null +++ b/src/test/java/PurgeCommandTest.java @@ -0,0 +1,44 @@ +import command.Data; +import command.PurgeCommand; +import inventory.Medicine; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author alvintan01 + +public class PurgeCommandTest { + private final InputStream sysIn = System.in; + + @BeforeEach + public void getData() { + Data.generateTestData(); + } + + @AfterEach + public void tearDown() { + System.setIn(sysIn); + Medicine.getInstance().clear(); + } + + @Test + public void purgeCommand_inputN_expectDataNotCleared() { + ByteArrayInputStream in = new ByteArrayInputStream("N\n".getBytes()); + System.setIn(in); // Send 'N' to scanner + new PurgeCommand().execute(); + assertEquals(Medicine.getInstance().size(), 18); + } + + @Test + public void purgeCommand_inputY_expectDataCleared() { + ByteArrayInputStream in = new ByteArrayInputStream("Y\n".getBytes()); + System.setIn(in); // Send 'Y' to scanner + new PurgeCommand().execute(); + assertEquals(Medicine.getInstance().size(), 0); + } +} diff --git a/src/test/java/command/Data.java b/src/test/java/command/Data.java new file mode 100644 index 0000000000..6fa87cab07 --- /dev/null +++ b/src/test/java/command/Data.java @@ -0,0 +1,109 @@ +package command; + +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; +import utilities.parser.DateParser; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; + +//@@author alvintan01 + +public class Data { + private static Ui ui = Ui.getInstance(); + + /** + * Function to clear all the data and reset the IDs. + */ + public static void clearTestData() { + ArrayList medicines = Medicine.getInstance(); + // Clear all data first and reset count + medicines.clear(); + Stock.setStockCount(0); + Prescription.setPrescriptionCount(0); + Order.setOrderCount(0); + } + + /** + * Function to generate test data to test list commands. + */ + public static void generateTestData() { + clearTestData(); + ArrayList medicines = Medicine.getInstance(); + generateTestStocks(medicines); + generateTestPrescriptions(medicines); + generateTestOrders(medicines); + } + + /** + * Function to generate stock data to test list commands. + * + * @param medicines Arraylist of medicines. + */ + public static void generateTestStocks(ArrayList medicines) { + try { + medicines.add(new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2022"), + "HEADACHES", 1000)); + medicines.add(new Stock("PANADOL", 20, 10, DateParser.stringToDate("14-9-2022"), + "HEADACHES", 1000)); + medicines.add(new Stock("VICODIN", 10, 20, DateParser.stringToDate("30-9-2022"), + "SEVERE PAIN", + 500)); + medicines.add(new Stock("SIMVASTATIN", 20, 25, DateParser.stringToDate("10-10-2023"), + "HIGH CHOLESTEROL", 800)); + medicines.add(new Stock("LISINOPRIL", 20, 25, DateParser.stringToDate("15-10-2023"), + "HYPOTHYROIDISM", 800)); + medicines.add(new Stock("AZITHROMYCIN", 20, 35, DateParser.stringToDate("15-10-2023"), + "INFECTIONS", 100)); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + + } + + /** + * Function to generate prescription data to test list commands. + * + * @param medicines Arraylist of medicines. + */ + public static void generateTestPrescriptions(ArrayList medicines) { + try { + medicines.add(new Prescription("PANADOL", 10, "S1234567A", + DateParser.stringToDate("9-10-2021"), "Jane", 1)); + medicines.add(new Prescription("VICODIN", 15, "S2345678B", + DateParser.stringToDate("10-10-2021"), "Peter", 3)); + medicines.add(new Prescription("SIMVASTATIN", 20, "S1234567A", + DateParser.stringToDate("11-10-2021"), "Sam", 4)); + medicines.add(new Prescription("LISINOPRIL", 10, "S3456789C", + DateParser.stringToDate("12-10-2021"), "Jane", 5)); + medicines.add(new Prescription("AZITHROMYCIN", 5, "S2345678B", + DateParser.stringToDate("13-10-2021"), "Peter", 6)); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + } + + /** + * Function to generate order data to test list commands. + * + * @param medicines Arraylist of medicines. + */ + public static void generateTestOrders(ArrayList medicines) { + try { + medicines.add(new Order("PANADOL", 100, DateParser.stringToDate("9-10-2021"))); + medicines.add(new Order("VICODIN", 30, DateParser.stringToDate("10-10-2021"))); + Order order = new Order("VICODIN", 50, DateParser.stringToDate("11-10-2021")); + order.setDelivered(); + medicines.add(order); + medicines.add(new Order("SIMVASTATIN", 20, DateParser.stringToDate("11-10-2021"))); + medicines.add(new Order("LISINOPRIL", 200, DateParser.stringToDate("12-12-2021"))); + medicines.add(new Order("AZITHROMYCIN", 100, DateParser.stringToDate("13-12-2021"))); + medicines.add(new Order("PANADOL", 50, DateParser.stringToDate("13-12-2021"))); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + } +} diff --git a/src/test/java/command/order/AddOrderCommandTest.java b/src/test/java/command/order/AddOrderCommandTest.java new file mode 100644 index 0000000000..c7a0d88c4e --- /dev/null +++ b/src/test/java/command/order/AddOrderCommandTest.java @@ -0,0 +1,145 @@ +package command.order; + +import command.Command; +import command.stock.ListStockCommand; +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import utilities.parser.DateParser; + +import java.io.ByteArrayOutputStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +//@@author jiangweichen835 +public class AddOrderCommandTest { + public static final String NAME = "Panadol"; + public static final String QUANTITY = "50"; + public static final String DATE = "10-10-2020"; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + ArrayList medicines = Medicine.getInstance(); + + @BeforeEach + void setup() { + medicines.clear(); + Stock.setStockCount(0); + Order.setOrderCount(0); + Prescription.setPrescriptionCount(0); + System.setOut(new PrintStream(outContent)); + } + + private void executeAddOrderCommand(String quantity, String date) { + LinkedHashMap parameters = new LinkedHashMap<>(); + + parameters.put("n", NAME); + parameters.put("q", quantity); + parameters.put("d", date); + + Command command = new AddOrderCommand(parameters); + command.execute(); + } + + @Test + public void addOrderCommand_validAddOrder_expectValid() { + + executeAddOrderCommand(QUANTITY, DATE); + + String expectedOutput = "Order added: Panadol\n" + + "+====+=========+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=========+==========+============+=========+\n" + + "| 1 | PANADOL | 50 | 10-10-2020 | PENDING | \n" + + "+----+---------+----------+------------+---------+\n"; + + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + } + + @Test + public void addOrderCommand_invalidDate_expectInvalid() { + executeAddOrderCommand(QUANTITY, "10 Oct 2020"); + + String expectedOutput = "Invalid date! Ensure date is in dd-MM-yyyy."; + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + } + + /** + * If medicine name exists in Order and in Stock. + */ + @Test + public void addOrderCommand_exceedMaxQuantity_expectInvalid() { + try { + medicines.add(new Stock("PANADOL", 10, 50, + DateParser.stringToDate("10-10-2020"), "For Fever", 100)); + medicines.add(new Order("PANADOL", 50, DateParser.stringToDate("9-10-2022"))); + } catch (ParseException e) { + e.printStackTrace(); + } + executeAddOrderCommand("1000", DATE); + + String expectedOutput = "Unable to add order as total order quantity exceeds maximum stock quantity of 100.\n" + + "Existing quantity in stock: 50\n" + "Pending order quantity: 50"; + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + } + + /** + * If medicine name exists in Order but not in Stock. + * There is no max quantity to limit user input quantity. + */ + @Test + public void addOrderCommand_validQuantity_expectValid() { + try { + medicines.add(new Order("PANADOL", 50, DateParser.stringToDate("9-10-2020"))); + } catch (ParseException e) { + e.printStackTrace(); + } + executeAddOrderCommand("1000", DATE); + + String expectedOutput = "Order added: Panadol\n" + + "+====+=========+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=========+==========+============+=========+\n" + + "| 2 | PANADOL | 1000 | 10-10-2020 | PENDING | \n" + + "+----+---------+----------+------------+---------+\n"; + + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + } + + /** + * If medicine exist in Stock but not in Order. + */ + @Test + public void addOrderCommand_InvalidQuantity_expectInvalid() { + try { + medicines.add(new Stock("PANADOL", 10, 20, + DateParser.stringToDate("10-10-2020"), "Fever", 1000)); + } catch (ParseException e) { + e.printStackTrace(); + } + executeAddOrderCommand("1000", DATE); + + String expectedOutput = "Unable to add order as total order quantity exceeds " + + "maximum stock quantity of 1000.\nExisting quantity in stock: 20"; + + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + } + + @Test + public void addOrderCommand_invalidQuantity_expectInvalid() { + executeAddOrderCommand("0", DATE); + + String expectedOutput = "Order Quantity cannot be 0."; + + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/order/ArchiveOrderCommandTest.java b/src/test/java/command/order/ArchiveOrderCommandTest.java new file mode 100644 index 0000000000..eedc4e9a60 --- /dev/null +++ b/src/test/java/command/order/ArchiveOrderCommandTest.java @@ -0,0 +1,110 @@ +package command.order; + +import command.Command; +import command.Data; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author RemusTeo + +public class ArchiveOrderCommandTest { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUp() { + Data.generateTestData(); + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void tearDown() { + System.setOut(originalOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + private void executeArchiveOrderCommand(String parameter, String parameterValue) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put(parameter, parameterValue); + Command command = new ArchiveOrderCommand(parameters); + command.execute(); + } + + private void executeListOrderCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + Command command = new ListOrderCommand(parameters); + command.execute(); + } + + @Test + public void archiveOrderCommand_deliveredOrderDoesNotExist_expectZeroArchived() { + executeArchiveOrderCommand("d", "10-10-2021"); + assertEquals("Archived 0 delivered orders from 10-10-2021", outContent.toString().trim()); + } + + @Test + public void archiveOrderCommand_deliveredOrdersExist_expectOneArchived() { + executeArchiveOrderCommand("d", "11-10-2021"); + assertEquals("Archived 1 delivered orders from 11-10-2021", outContent.toString().trim()); + } + + @Test + public void archiveOrderCommand_deliveredOrdersRemoved_expectOneLessOrder() { + archiveOrderCommand_deliveredOrdersExist_expectOneArchived(); + executeListOrderCommand(); + String expectedOutput = "Archived 1 delivered orders from 11-10-2021\n" + + "+====+==============+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+=========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } + + @Test + public void archiveOrderCommand_noDeliveredOrdersToRemove_expectSameOrder() { + archiveOrderCommand_deliveredOrderDoesNotExist_expectZeroArchived(); + executeListOrderCommand(); + String expectedOutput = "Archived 0 delivered orders from 10-10-2021\n" + + "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/order/DeleteOrderCommandTest.java b/src/test/java/command/order/DeleteOrderCommandTest.java new file mode 100644 index 0000000000..ce1b6c6e9d --- /dev/null +++ b/src/test/java/command/order/DeleteOrderCommandTest.java @@ -0,0 +1,79 @@ +package command.order; + +import command.Command; +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import utilities.parser.DateParser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author deonchung + +public class DeleteOrderCommandTest { + + public static final String ID = "1"; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + ArrayList medicines = Medicine.getInstance(); + + @BeforeEach + void setup() { + medicines.clear(); + Stock.setStockCount(0); + Order.setOrderCount(0); + Prescription.setPrescriptionCount(0); + System.setOut(new PrintStream(outContent)); + + } + + @Test + void deleteOrderCommand_validDeleteOrder_expectValid() { + try { + Order order = new Order("PANADOL", 10, DateParser.stringToDate("12-12-2025")); + medicines.add(order); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeDeleteOrderCommand(); + + String expectedOutput = "Order deleted for Order ID 1"; + + assertEquals(expectedOutput.trim(), outContent.toString().trim()); + + } + + @Test + void deleteOrderCommand_invalidId_expectInvalid() { + executeDeleteOrderCommand(); + + String expectedOutput = "Invalid order id provided!"; + + assertEquals(expectedOutput.trim(), outContent.toString().trim()); + + } + + private void executeDeleteOrderCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + + parameters.put("i", ID); + + Command command = new DeleteOrderCommand(parameters); + command.execute(); + + } + +} + diff --git a/src/test/java/command/order/ListOrderCommandTest.java b/src/test/java/command/order/ListOrderCommandTest.java new file mode 100644 index 0000000000..37316703b1 --- /dev/null +++ b/src/test/java/command/order/ListOrderCommandTest.java @@ -0,0 +1,343 @@ +package command.order; + +import command.Command; +import command.Data; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListOrderCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeAll + public static void getData() { + Data.generateTestData(); + } + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + private void executeListOrderCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + Command command = new ListOrderCommand(parameters); + command.execute(); + } + + private void executeListOrderCommand(String parameter, String parameterValue) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put(parameter, parameterValue); + Command command = new ListOrderCommand(parameters); + command.execute(); + } + + //@@author alvintan01 + + @Test + public void listOrderCommand_sortByIdAscending_expectOrdersWithSortedIdAscending() { + String expectedOutput = "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+"; + executeListOrderCommand("sort", "i"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_sortByNameAscending_expectOrdersWithSortedNameAscending() { + String expectedOutput = "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+"; + executeListOrderCommand("sort", "n"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_sortByQuantityDescending_expectOrdersWithSortedQuantityDescending() { + String expectedOutput = "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+"; + executeListOrderCommand("rsort", "q"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_sortByDateDescending_expectOrdersWithSortedDateDescending() { + String expectedOutput = "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+"; + executeListOrderCommand("rsort", "d"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_sortByStatusDescending_expectOrdersWithSortedStatusDescending() { + String expectedOutput = "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+"; + executeListOrderCommand("rsort", "s"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_columnDoesNotExist_expectError() { + String expectedOutput = "Invalid column name/alias! Column names can only be [ID, NAME, QUANTITY, DATE, " + + "STATUS] and the respective aliases are [i, n, q, d, s]."; + executeListOrderCommand("sort", "a"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + //@@author RemusTeo + @Test + public void listOrderCommand_withoutParameters_expectAllOrders() { + executeListOrderCommand(); + String expectedOutput = "+====+==============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+===========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+-----------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidId_expectValidSingleOrder() { + executeListOrderCommand("i", "7"); + String expectedOutput = "+====+=========+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=========+==========+============+=========+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+---------+----------+------------+---------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byInvalidId_expectInvalid() { + String[] invalidParams = {"-1", "0", "8"}; + for (String param : invalidParams) { + executeListOrderCommand("i", param); + String expectedOutput = "Invalid order id provided!"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + outputStream.reset(); + } + } + + @Test + public void listOrderCommand_byValidName_expectOneMatchingOrder() { + executeListOrderCommand("n", "SIMVASTATIN"); + String expectedOutput = "+====+=============+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=============+==========+============+=========+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+-------------+----------+------------+---------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidName_expectTwoMatchingOrders() { + executeListOrderCommand("n", "VICODIN"); + String expectedOutput = "+====+=========+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=========+==========+============+===========+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+---------+----------+------------+-----------+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+---------+----------+------------+-----------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidName_expectNoMatch() { + executeListOrderCommand("n", "ABC"); + String expectedOutput = "There are no orders found."; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidQuantity_expectOneMatchingOrder() { + executeListOrderCommand("q", "200"); + String expectedOutput = "+====+============+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+============+==========+============+=========+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+------------+----------+------------+---------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidQuantity_expectNoMatch() { + executeListOrderCommand("q", "300"); + String expectedOutput = "There are no orders found."; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidDate_expectOneMatchingOrder() { + executeListOrderCommand("d", "9-10-2021"); + String expectedOutput = "+====+=========+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=========+==========+============+=========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+---------+----------+------------+---------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidDate_expectTwoMatchingOrder() { + executeListOrderCommand("d", "11-10-2021"); + String expectedOutput = "+====+=============+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=============+==========+============+===========+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+-------------+----------+------------+-----------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+-------------+----------+------------+-----------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidDate_expectNoMatch() { + executeListOrderCommand("d", "01-10-2021"); + String expectedOutput = "There are no orders found."; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidStatusPending_expectAllPendingOrders() { + executeListOrderCommand("s", "PENDING"); + String expectedOutput = "+====+==============+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+==============+==========+============+=========+\n" + + "| 1 | PANADOL | 100 | 09-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 2 | VICODIN | 30 | 10-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 4 | SIMVASTATIN | 20 | 11-10-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 5 | LISINOPRIL | 200 | 12-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 6 | AZITHROMYCIN | 100 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+\n" + + "| 7 | PANADOL | 50 | 13-12-2021 | PENDING | \n" + + "+----+--------------+----------+------------+---------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byValidStatusDelivered_expectAllDeliveredOrders() { + executeListOrderCommand("s", "DELIVERED"); + String expectedOutput = "+====+=========+==========+============+===========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=========+==========+============+===========+\n" + + "| 3 | VICODIN | 50 | 11-10-2021 | DELIVERED | \n" + + "+----+---------+----------+------------+-----------+"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listOrderCommand_byInvalidStatus_expectInvalid() { + executeListOrderCommand("s", "ABC"); + String expectedOutput = "Invalid status! Ensure status is either PENDING or DELIVERED"; + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/order/ReceiveOrderTest.java b/src/test/java/command/order/ReceiveOrderTest.java new file mode 100644 index 0000000000..7748af52ba --- /dev/null +++ b/src/test/java/command/order/ReceiveOrderTest.java @@ -0,0 +1,155 @@ +package command.order; + +import command.Data; +import inventory.Medicine; +import inventory.Order; +import inventory.Stock; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import utilities.parser.DateParser; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author alvintan01 + +public class ReceiveOrderTest { + private static ArrayList medicines = Medicine.getInstance(); + + @BeforeEach + @AfterEach + public void clearData() { + Data.clearTestData(); + } + + @Test + public void receiveOrder_newOrder_expectOrderCompleted() { + try { + Order order = new Order("PANADOL", 100, DateParser.stringToDate("3-11-2021")); + medicines.add(order); + + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", "1"); + parameters.put("p", "20"); + parameters.put("e", "11-12-2022"); + parameters.put("d", "FEVER"); + parameters.put("m", "1000"); + + new ReceiveOrderCommand(parameters).execute(); + + final int orderIndex = 0; + final int stockIndex = 1; + + Order completedOrder = (Order) medicines.get(orderIndex); + Stock addedStock = (Stock) medicines.get(stockIndex); + + assertEquals(completedOrder.getStatus(), "DELIVERED"); + assertEquals(addedStock.getMedicineName(), "PANADOL"); + assertEquals(addedStock.getQuantity(), 100); + assertEquals(DateParser.dateToString(addedStock.getExpiry()), "11-12-2022"); + assertEquals(addedStock.getDescription(), "FEVER"); + assertEquals(addedStock.getMaxQuantity(), 1000); + + assertEquals(addedStock.getMaxQuantity(), 1000); + } catch (ParseException e) { + System.out.println("Unable to parse date!"); + } + } + + @Test + public void receiveOrder_newOrderStockExists_expectDescriptionMaxQuantityIgnoredOrderCompleted() { + try { + Stock stock = new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2022"), + "HEADACHES", 1000); + Order order = new Order("PANADOL", 100, DateParser.stringToDate("3-11-2021")); + medicines.add(stock); + medicines.add(order); + + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", "1"); + parameters.put("p", "20"); + parameters.put("e", "11-12-2022"); + parameters.put("d", "ANOTHER DESCRIPTION"); + parameters.put("m", "10000"); + + new ReceiveOrderCommand(parameters).execute(); + + final int orderIndex = 1; + final int stockIndex = 2; + + Order completedOrder = (Order) medicines.get(orderIndex); + Stock addedStock = (Stock) medicines.get(stockIndex); + + assertEquals(completedOrder.getStatus(), "DELIVERED"); + assertEquals(addedStock.getDescription(), "HEADACHES"); + assertEquals(addedStock.getMaxQuantity(), 1000); + } catch (ParseException e) { + System.out.println("Unable to parse date!"); + } + } + + @Test + public void receiveOrder_newOrderStockExists_expectMaxQuantityErrorOrderPendingSameExistingQuantity() { + try { + Stock stock = new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2022"), + "HEADACHES", 100); + Order order = new Order("PANADOL", 100, DateParser.stringToDate("3-11-2021")); + medicines.add(stock); + medicines.add(order); + final int numberOfMedicines = medicines.size(); + + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", "1"); + parameters.put("p", "20"); + parameters.put("e", "11-12-2022"); + + new ReceiveOrderCommand(parameters).execute(); + + final int orderIndex = 1; + final int stockIndex = 0; + + Order completedOrder = (Order) medicines.get(orderIndex); + Stock currentStock = (Stock) medicines.get(stockIndex); + + assertEquals(medicines.size(), numberOfMedicines); + assertEquals(completedOrder.getStatus(), "PENDING"); + assertEquals(currentStock.getQuantity(), 20); + + } catch (ParseException e) { + System.out.println("Unable to parse date!"); + } + } + + @Test + public void receiveOrder_newOrderStockExistsSameExpiry_expectPriceIgnoredOrderCompletedStockQuantityIncreased() { + try { + Stock stock = new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2022"), + "HEADACHES", 1000); + Order order = new Order("PANADOL", 100, DateParser.stringToDate("3-11-2021")); + medicines.add(stock); + medicines.add(order); + + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", "1"); + parameters.put("p", "30"); + parameters.put("e", "13-9-2022"); + + new ReceiveOrderCommand(parameters).execute(); + + final int orderIndex = 1; + final int stockIndex = 0; + + Order completedOrder = (Order) medicines.get(orderIndex); + Stock addedStock = (Stock) medicines.get(stockIndex); + + assertEquals(completedOrder.getStatus(), "DELIVERED"); + assertEquals(addedStock.getQuantity(), 120); + } catch (ParseException e) { + System.out.println("Unable to parse date!"); + } + } +} diff --git a/src/test/java/command/order/UpdateOrderCommandTest.java b/src/test/java/command/order/UpdateOrderCommandTest.java new file mode 100644 index 0000000000..5c90edcf2a --- /dev/null +++ b/src/test/java/command/order/UpdateOrderCommandTest.java @@ -0,0 +1,75 @@ +package command.order; + +import command.Command; +import command.Data; +import inventory.Medicine; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author a-tph + +class UpdateOrderCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + Data.generateTestData(); + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + private void executeUpdateOrderCommand(String id, String date, String qty) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("d", date); + parameters.put("q", qty); + Command command = new UpdateOrderCommand(parameters); + command.execute(); + } + + @Test + public void updateOrder_validFields_expectValidUpdate() { + String expectedOutput = "Updated! Number of rows affected: 1\n" + + "+====+=============+==========+============+=========+\n" + + "| ID | NAME | QUANTITY | DATE | STATUS | \n" + + "+====+=============+==========+============+=========+\n" + + "| 4 | SIMVASTATIN | 20 | 01-01-2021 | PENDING | \n" + + "+----+-------------+----------+------------+---------+"; + executeUpdateOrderCommand("4", "01-01-2021", "20"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateOrder_validFieldsAndInvalidDate_expectInvalidUpdate() { + String expectedOutput = "Invalid date! Input date cannot be after today's date."; + executeUpdateOrderCommand("4", "01-01-2099", "20"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateOrder_validFieldsAndInvalidQuantity_expectInvalidUpdate() { + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 99999, Max Quantity: 800"; + executeUpdateOrderCommand("4", "01-01-2021", "99999"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + +} diff --git a/src/test/java/command/prescription/AddPrescriptionCommandTest.java b/src/test/java/command/prescription/AddPrescriptionCommandTest.java new file mode 100644 index 0000000000..99675d6d34 --- /dev/null +++ b/src/test/java/command/prescription/AddPrescriptionCommandTest.java @@ -0,0 +1,124 @@ +package command.prescription; + +import command.Command; +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import utilities.parser.DateParser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author deonchung + +public class AddPrescriptionCommandTest { + + public static final String NAME = "Panadol"; + public static final String QUANTITY = "30"; + public static final String CUSTOMER_ID = "123"; + public static final String STAFF_NAME = "JOHN"; + public static final String DESCRIPTION = "FEVER"; + public static final int MAX_QUANTITY = 1000; + public static final int PRICE = 10; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + ArrayList medicines = Medicine.getInstance(); + + @BeforeEach + void setup() { + medicines.clear(); + Stock.setStockCount(0); + Order.setOrderCount(0); + Prescription.setPrescriptionCount(0); + System.setOut(new PrintStream(outContent)); + } + + @Test + void addPrescriptionCommand_validPrescription_expectValid() { + try { + Stock stock = new Stock(NAME, PRICE, 50, + DateParser.stringToDate("11-11-2025"), DESCRIPTION, MAX_QUANTITY); + medicines.add(stock); + + executeAddPrescriptionCommand(); + + final int prescriptionIndex = 1; + Prescription prescription = (Prescription) medicines.get(prescriptionIndex); + + assertEquals(prescription.getPrescriptionId(), 1); + assertEquals(prescription.getStockId(), 1); + assertEquals(prescription.getQuantity(), 30); + assertEquals(prescription.getStaff(), "JOHN"); + assertEquals(prescription.getCustomerId(), "123"); + Date today = new Date(); + assertEquals(DateParser.dateToString(prescription.getDate()), DateParser.dateToString(today)); + + } catch (ParseException e) { + e.printStackTrace(); + } + + } + + @Test + void addPrescriptionCommand_exceedQuantity_expectInvalid() { + try { + Stock stock = new Stock(NAME, PRICE, 5, + DateParser.stringToDate("11-11-2025"), DESCRIPTION, MAX_QUANTITY); + medicines.add(stock); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeAddPrescriptionCommand(); + + String expectedOutput = "Unable to Prescribe! Prescription quantity is more than stock available!\n" + + "Prescription quantity: 30 Stock available: 5"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + @Test + void addPrescriptionCommand_invalidExpiryDate_expectInvalid() { + try { + Stock stock = new Stock(NAME, PRICE, 5, + DateParser.stringToDate("11-11-2020"), DESCRIPTION, MAX_QUANTITY); + medicines.add(stock); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeAddPrescriptionCommand(); + + String expectedOutput = "Unable to Prescribe! Medication has expired!"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + private void executeAddPrescriptionCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + + parameters.put("n", NAME); + parameters.put("q", QUANTITY); + parameters.put("c", CUSTOMER_ID); + parameters.put("s", STAFF_NAME); + + Command command = new AddPrescriptionCommand(parameters); + command.execute(); + + } +} diff --git a/src/test/java/command/prescription/ArchivePrescriptionCommandTest.java b/src/test/java/command/prescription/ArchivePrescriptionCommandTest.java new file mode 100644 index 0000000000..dc8afaf0d3 --- /dev/null +++ b/src/test/java/command/prescription/ArchivePrescriptionCommandTest.java @@ -0,0 +1,100 @@ +package command.prescription; + +import command.Command; +import command.Data; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author RemusTeo + +public class ArchivePrescriptionCommandTest { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUp() { + Data.generateTestData(); + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void tearDown() { + System.setOut(originalOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + private void executeArchivePrescriptionCommand(String parameter, String parameterValue) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put(parameter, parameterValue); + Command command = new ArchivePrescriptionCommand(parameters); + command.execute(); + } + + private void executeListPrescriptionCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + Command command = new ListPrescriptionCommand(parameters); + command.execute(); + } + + @Test + public void archivePrescriptionCommand_prescriptionsDoesNotExist_expectZeroArchived() { + executeArchivePrescriptionCommand("d", "08-10-2021"); + assertEquals("Archived 0 prescriptions from 08-10-2021", outContent.toString().trim()); + } + + @Test + public void archivePrescriptionCommand_prescriptionsExist_expectTwoArchived() { + executeArchivePrescriptionCommand("d", "10-10-2021"); + assertEquals("Archived 2 prescriptions from 10-10-2021", outContent.toString().trim()); + } + + @Test + public void archivePrescriptionCommand_prescriptionsRemoved_expectTwoLessPrescriptions() { + archivePrescriptionCommand_prescriptionsExist_expectTwoArchived(); + executeListPrescriptionCommand(); + String expectedOutput = "Archived 2 prescriptions from 10-10-2021\n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } + + @Test + public void archivePrescriptionCommand_noPrescriptionsToRemove_expectSamePrescriptions() { + archivePrescriptionCommand_prescriptionsDoesNotExist_expectZeroArchived(); + executeListPrescriptionCommand(); + String expectedOutput = "Archived 0 prescriptions from 08-10-2021\n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/prescription/DeletePrescriptionCommandTest.java b/src/test/java/command/prescription/DeletePrescriptionCommandTest.java new file mode 100644 index 0000000000..1ddebbbde7 --- /dev/null +++ b/src/test/java/command/prescription/DeletePrescriptionCommandTest.java @@ -0,0 +1,109 @@ +package command.prescription; + +import command.Command; +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import utilities.parser.DateParser; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author deonchung + +public class DeletePrescriptionCommandTest { + public static final String ID = "1"; + public static final String MEDICATION_NAME = "PANADOL"; + public static final String CUSTOMER_ID = "123"; + public static final String PRESCRIPTION_DATE = "12-12-2025"; + public static final String STAFF_NAME = "JOHN"; + public static final String DESCRIPTION = "For Fever"; + public static final int MAX_QUANTITY = 50; + public static final int STOCK_ID = 1; + public static final int STOCK_QUANTITY = 45; + public static final int PRICE = 10; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + ArrayList medicines = Medicine.getInstance(); + + @BeforeEach + void setup() { + medicines.clear(); + Stock.setStockCount(0); + Order.setOrderCount(0); + Prescription.setPrescriptionCount(0); + System.setOut(new PrintStream(outContent)); + + } + + @Test + void deletePrescriptionCommand_validDeletePrescription_expectValid() { + try { + medicines.add(new Prescription(MEDICATION_NAME, PRICE, CUSTOMER_ID, + DateParser.stringToDate(PRESCRIPTION_DATE), STAFF_NAME, STOCK_ID)); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeDeletePrescriptionCommand(); + + String expectedOutput = "Prescription deleted for Prescription ID 1"; + + assertEquals(expectedOutput.trim(), outContent.toString().trim()); + + } + + @Test + void deletePrescriptionCommand_invalidPrescriptionId_expectInvalid() { + String expectedOutput = "Invalid prescription id provided!"; + + executeDeletePrescriptionCommand(); + + assertEquals(expectedOutput.trim(), outContent.toString().trim()); + + } + + @Test + void deletePrescriptionCommand_invalidPrescriptionQuantity_expectInvalid() { + try { + Stock stock = new Stock(MEDICATION_NAME, PRICE, STOCK_QUANTITY, + DateParser.stringToDate("11-11-2025"), DESCRIPTION, MAX_QUANTITY); + medicines.add(stock); + Prescription prescription = new Prescription(MEDICATION_NAME, 10, CUSTOMER_ID, + DateParser.stringToDate(PRESCRIPTION_DATE), STAFF_NAME, STOCK_ID); + medicines.add(prescription); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeDeletePrescriptionCommand(); + + String expectedOutput = "Unable to delete prescription. Quantity added will exceed maximum quantity.\n" + + "Maximum quantity: 50 Total Quantity after deletion: 55"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + private void executeDeletePrescriptionCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + + parameters.put("i", ID); + + Command command = new DeletePrescriptionCommand(parameters); + command.execute(); + + } + +} diff --git a/src/test/java/command/prescription/ListPrescriptionCommandTest.java b/src/test/java/command/prescription/ListPrescriptionCommandTest.java new file mode 100644 index 0000000000..4461a406e5 --- /dev/null +++ b/src/test/java/command/prescription/ListPrescriptionCommandTest.java @@ -0,0 +1,269 @@ +package command.prescription; + +import command.Data; +import inventory.Medicine; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author alvintan01 + +public class ListPrescriptionCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeAll + public static void getData() { + Data.generateTestData(); + } + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @AfterAll + public static void clearData() { + Medicine.getInstance().clear(); + } + + public void executeListPrescriptionCommand(String parameter, String parameterValue) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put(parameter, parameterValue); + new ListPrescriptionCommand(parameters).execute(); + } + + @Test + public void listPrescription_filterByIdTwo_expectPrescriptionsWithIdTwo() { + String expectedOutput = "+====+=========+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+---------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("i", "2"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_filterByNamePanadol_expectPrescriptionsWithPanadol() { + String expectedOutput = "+====+=========+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+---------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("n", "panadol"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_filterByQuantityTen_expectPrescriptionsWithTenQuantity() { + String expectedOutput = "+====+============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+============+==========+=============+============+=======+==========+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("q", "10"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_filterByCustomerIdS1234567A_expectPrescriptionsWithCustomerIdS1234567A() { + String expectedOutput = "+====+=============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=============+==========+=============+============+=======+==========+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+-------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+-------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("c", "S1234567A"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_filterByDate13October2021_expectPrescriptionsWithDate13October2021() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("d", "13-10-2021"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_filterByStaffJane_expectPrescriptionsWithStaffJane() { + String expectedOutput = "+====+============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+============+==========+=============+============+=======+==========+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("s", "jane"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_filterByStockId1_expectPrescriptionsWithStockId1() { + String expectedOutput = "+====+=========+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+---------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("sid", "1"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_sortByIdDescending_expectPrescriptionsWithSortedIdDescending() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("rsort", "i"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_sortByNameAscending_expectPrescriptionsWithSortedNameAscending() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("sort", "n"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_sortByQuantityAscending_expectPrescriptionsWithSortedQuantityAscending() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("sort", "q"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_sortByDateDescending_expectPrescriptionsWithSortedDateDescending() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("rsort", "d"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_sortByStaffDescending_expectPrescriptionsWithSortedStaffDescending() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("rsort", "s"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_sortByStockIdDescending_expectPrescriptionsWithSortedStockIdDescending() { + String expectedOutput = "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 5 | AZITHROMYCIN | 5 | S2345678B | 13-10-2021 | PETER | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 4 | LISINOPRIL | 10 | S3456789C | 12-10-2021 | JANE | 5 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 3 | SIMVASTATIN | 20 | S1234567A | 11-10-2021 | SAM | 4 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 2 | VICODIN | 15 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+\n" + + "| 1 | PANADOL | 10 | S1234567A | 09-10-2021 | JANE | 1 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeListPrescriptionCommand("rsort", "sid"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_invalidParameter_expectError() { + String expectedOutput = "Please enter a valid optional parameter!\n" + + "COMMAND SYNTAX: listprescription {i/ID n/NAME q/QUANTITY c/CUSTOMER_ID d/DATE s/STAFF_NAME " + + "sid/STOCK_ID sort/COLUMN_NAME rsort/COLUMN_NAME}"; + executeListPrescriptionCommand("a", "1"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_nameDoesNotExist_expectNoResultsFound() { + String expectedOutput = "There are no records of medicines prescribed."; + executeListPrescriptionCommand("n", "abcd"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listPrescription_columnDoesNotExist_expectError() { + String expectedOutput = "Invalid column name/alias! Column names can only be [ID, NAME, QUANTITY, CUSTOMER_" + + "ID, DATE, STAFF, STOCK_ID] and the respective aliases are [i, n, q, c, d, s, sid]."; + executeListPrescriptionCommand("sort", "a"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/prescription/UpdatePrescriptionCommandTest.java b/src/test/java/command/prescription/UpdatePrescriptionCommandTest.java new file mode 100644 index 0000000000..becd269b1e --- /dev/null +++ b/src/test/java/command/prescription/UpdatePrescriptionCommandTest.java @@ -0,0 +1,156 @@ +package command.prescription; + +import command.Command; +import command.Data; +import inventory.Medicine; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author a-tph + +class UpdatePrescriptionCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + Data.generateTestData(); + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + private void executeUpdatePrescriptionCommandNameAndQty(String id, String name, String qty) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("n", name); + parameters.put("q", qty); + Command command = new UpdatePrescriptionCommand(parameters); + command.execute(); + } + + private void executeUpdatePrescriptionCommandName(String id, String name) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("n", name); + Command command = new UpdatePrescriptionCommand(parameters); + command.execute(); + } + + private void executeUpdatePrescriptionCommandQty(String id, String qty) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("q", qty); + Command command = new UpdatePrescriptionCommand(parameters); + command.execute(); + } + + private void executeUpdatePrescriptionCommand(String id, String customerId, String staff, String date) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("c", customerId); + parameters.put("s", staff); + parameters.put("d", date); + Command command = new UpdatePrescriptionCommand(parameters); + command.execute(); + } + + @Test + public void updatePrescription_ValidNameAndQty_expectValidUpdate() { + String expectedOutput = "Restored 10 PANADOL\n" + + "Updated prescription information!\n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+==============+==========+=============+============+=======+==========+\n" + + "| 6 | AZITHROMYCIN | 5 | S1234567A | 09-10-2021 | JANE | 6 | \n" + + "+----+--------------+----------+-------------+------------+-------+----------+"; + executeUpdatePrescriptionCommandNameAndQty("1", "azithromycin", "5"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_InvalidNameValidQty_expectInvalidUpdate() { + String expectedOutput = "Action aborted! Either medication not found or stock has expired."; + executeUpdatePrescriptionCommandNameAndQty("1", "notFound", "10000"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_ValidNameInvalidQty_expectInvalidUpdate() { + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 9999, Max Quantity: 35\n" + + "Prescription of medication aborted!"; + executeUpdatePrescriptionCommandNameAndQty("1", "azithromycin", "9999"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_validName_expectValidUpdate() { + String expectedOutput = "Restored 5 AZITHROMYCIN\n" + + "Updated prescription information!\n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| 6 | PANADOL | 5 | S2345678B | 13-10-2021 | PETER | 1 | \n" + + "+----+---------+----------+-------------+------------+-------+----------+"; + executeUpdatePrescriptionCommandName("5", "panadol"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_invalidName_expectInvalidUpdate() { + String expectedOutput = "Action aborted! Either medication not found or stock has expired."; + executeUpdatePrescriptionCommandName("5", "notFound"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_validQty_expectValidUpdate() { + String expectedOutput = "Restored 10 VICODIN\n" + + "Updated prescription information!\n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| 6 | VICODIN | 5 | S2345678B | 10-10-2021 | PETER | 3 | \n" + + "+----+---------+----------+-------------+------------+-------+----------+"; + executeUpdatePrescriptionCommandQty("2", "5"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_invalidQty_expectInvalidUpdate() { + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 9984, Max Quantity: 20\n" + + "Prescription of medication aborted!"; + executeUpdatePrescriptionCommandQty("2", "9999"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updatePrescription_validOtherFields_expectValidUpdate() { + String expectedOutput = "Updated prescription information!\n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| ID | NAME | QUANTITY | CUSTOMER_ID | DATE | STAFF | STOCK_ID | \n" + + "+====+=========+==========+=============+============+=======+==========+\n" + + "| 2 | VICODIN | 15 | 321B | 01-01-2021 | ALICE | 3 | \n" + + "+----+---------+----------+-------------+------------+-------+----------+"; + executeUpdatePrescriptionCommand("2", "321B", "Alice", "01-01-2021"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/stock/AddStockCommandTest.java b/src/test/java/command/stock/AddStockCommandTest.java new file mode 100644 index 0000000000..b1e58fb62d --- /dev/null +++ b/src/test/java/command/stock/AddStockCommandTest.java @@ -0,0 +1,160 @@ +package command.stock; + +import command.Command; +import inventory.Medicine; +import inventory.Order; +import inventory.Prescription; +import inventory.Stock; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import utilities.parser.DateParser; + +import java.io.ByteArrayOutputStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author deonchung + +public class AddStockCommandTest { + + public static final String NAME = "Panadol"; + public static final String EXPIRY_DATE = "12-12-2025"; + public static final String DESCRIPTION = "Fever"; + public static final String PRICE = "5"; + public static final String QUANTITY = "50"; + public static final String MAX_QUANTITY = "100"; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + ArrayList medicines = Medicine.getInstance(); + + @BeforeEach + void setup() { + medicines.clear(); + Stock.setStockCount(0); + Order.setOrderCount(0); + Prescription.setPrescriptionCount(0); + System.setOut(new PrintStream(outContent)); + } + + @Test + void addStockCommand_invalidExpiryDateFormat_expectInvalid() { + executeAddStockCommand("20/12/2021", MAX_QUANTITY); + + String expectedOutput = "Invalid expiry date! Ensure date is in dd-MM-yyyy."; + + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + @Test + void addStockCommand_exceedMaxQuantity_expectInvalid() { + executeAddStockCommand(EXPIRY_DATE, "5"); + + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 50, Max Quantity: 5"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + @Test + void addStockCommand_expiredMedication_expectInvalid() { + executeAddStockCommand("12-12-2020", MAX_QUANTITY); + + String expectedOutput = "Unable to add medicine. Medicine has expired."; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + @Test + void addStockCommand_validAddStock_expectValid() { + executeAddStockCommand(EXPIRY_DATE, MAX_QUANTITY); + + String expectedOutput = "Medication added: Panadol\n" + + "+====+=========+=======+==========+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+=======+==========+=============+=============+==============+\n" + + "| 1 | PANADOL | $5.00 | 50 | 12-12-2025 | FEVER | 100 | \n" + + "+----+---------+-------+----------+-------------+-------------+--------------+"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + @Test + void addStockCommand_sameStockName_expectValid() { + try { + Stock stock = new Stock("PANADOL", 10, 50, + DateParser.stringToDate("11-11-2025"), "For Fever", 1000); + medicines.add(stock); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeAddStockCommand(EXPIRY_DATE, MAX_QUANTITY); + + String expectedOutput = "Medicine exists. Using existing description and maximum quantity.\n" + + "Medication added: Panadol\n" + + "+====+=========+=======+==========+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+=======+==========+=============+=============+==============+\n" + + "| 2 | PANADOL | $5.00 | 50 | 12-12-2025 | FOR FEVER | 1000 | \n" + + "+----+---------+-------+----------+-------------+-------------+--------------+"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + @Test + void addStockCommand_sameStockNameAndExpiry_expectValid() { + try { + Stock stock = new Stock("PANADOL", 10, 50, + DateParser.stringToDate("12-12-2025"), "For Fever", 1000); + medicines.add(stock); + } catch (ParseException e) { + e.printStackTrace(); + } + + executeAddStockCommand(EXPIRY_DATE, MAX_QUANTITY); + + String expectedOutput = "Same Medication and Expiry Date exist. Using existing price, " + + "description and maximum quantity" + + ". Updating existing quantity.\n" + + "+====+=========+========+==========+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==========+=============+=============+==============+\n" + + "| 1 | PANADOL | $10.00 | 100 | 12-12-2025 | FOR FEVER | 1000 | \n" + + "+----+---------+--------+----------+-------------+-------------+--------------+"; + + // Output stream will include \r for each line break + assertEquals(expectedOutput.trim(), outContent.toString().trim().replace("\r", "")); + + } + + private void executeAddStockCommand(String expiryDate, String maxQuantity) { + LinkedHashMap parameters = new LinkedHashMap<>(); + + parameters.put("n", NAME); + parameters.put("p", PRICE); + parameters.put("q", QUANTITY); + parameters.put("e", expiryDate); + parameters.put("d", DESCRIPTION); + parameters.put("m", maxQuantity); + + Command command = new AddStockCommand(parameters); + command.execute(); + + } + +} diff --git a/src/test/java/command/stock/DeleteStockCommandTest.java b/src/test/java/command/stock/DeleteStockCommandTest.java new file mode 100644 index 0000000000..40012b2e09 --- /dev/null +++ b/src/test/java/command/stock/DeleteStockCommandTest.java @@ -0,0 +1,130 @@ +package command.stock; + +import command.Command; +import command.Data; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author RemusTeo + +public class DeleteStockCommandTest { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + void setup() { + Data.generateTestData(); + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + void restoreStreams() { + System.setOut(originalOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + private void executeDeleteStockCommand(String parameter, String parameterValue) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put(parameter, parameterValue); + Command command = new DeleteStockCommand(parameters); + command.execute(); + } + + private void executeListStockCommand() { + LinkedHashMap parameters = new LinkedHashMap<>(); + Command command = new ListStockCommand(parameters); + command.execute(); + } + + @Test + void deleteStockCommand_invalidStockId_expectInvalid() { + String[] invalidParams = {"-1", "0", "7"}; + for (String param : invalidParams) { + executeDeleteStockCommand("i", param); + String expectedOutput = "Invalid stock id provided!"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + outContent.reset(); + } + } + + @Test + void deleteStockCommand_validStockId_expectValid() { + executeDeleteStockCommand("i", "1"); + assertEquals("Deleted row with Stock Id: 1", outContent.toString().trim()); + } + + @Test + void deleteStockCommand_sameStockIdTwice_expectInvalid() { + deleteStockCommand_validStockId_expectValid(); + executeDeleteStockCommand("i", "1"); + String expectedOutput = "Deleted row with Stock Id: 1\n" + "Invalid stock id provided!"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } + + @Test + void deleteStockCommand_validStockId_expectOneLessStock() { + deleteStockCommand_validStockId_expectValid(); + executeListStockCommand(); + String expectedOutput = "Deleted row with Stock Id: 1\n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } + + @Test + void deleteStockCommand_invalidStockId_expectSameStocks() { + deleteStockCommand_invalidStockId_expectInvalid(); + executeListStockCommand(); + String expectedOutput = "" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + assertEquals(expectedOutput, outContent.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/stock/ListStockCommandTest.java b/src/test/java/command/stock/ListStockCommandTest.java new file mode 100644 index 0000000000..06736adab4 --- /dev/null +++ b/src/test/java/command/stock/ListStockCommandTest.java @@ -0,0 +1,381 @@ +package command.stock; + +import command.Data; +import inventory.Medicine; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListStockCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeAll + public static void getData() { + Data.generateTestData(); + } + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @AfterAll + public static void clearData() { + Medicine.getInstance().clear(); + } + + public void executeListStockCommand(String parameter, String parameterValue) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put(parameter, parameterValue); + new ListStockCommand(parameters).execute(); + } + + // @@author jiangweichen835 + @Test + public void listStockCommand_filterByIdOne_expectStocksWithIdOne() { + String expectedOutput = + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + + executeListStockCommand("i", "1"); + // Output stream will include \r for each line break + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByNamePanadol_expectStocksWithNamePanadol() { + String expectedOutput = + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + + executeListStockCommand("n", "panadol"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByPriceTen_expectStocksWithPriceTen() { + String expectedOutput = + "+====+=========+========+=============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+=============+=============+=============+==============+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+---------+--------+-------------+-------------+-------------+--------------+"; + + executeListStockCommand("p", "10"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByQuantityTen_expectStocksWithQuantityTen() { + String expectedOutput = + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("q", "10"); + new ListStockCommand(parameters).execute(); + + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByQuantityLessThan30_expectStocksWithQuantityLessThan30() { + String expectedOutput = + "+====+=============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=============+========+==============+=============+==================+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+-------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+-------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+-------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+-------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+-------------+--------+--------------+-------------+------------------+--------------+"; + + executeListStockCommand("low", "30"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByExpiry13Sep2022_expectStocksWithExpiry13Sep2022() { + String expectedOutput = + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + + executeListStockCommand("e", "13-09-2022"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByExpiryBefore31Dec2022_expectStocksWithExpiryBefore31Dec2022() { + String expectedOutput = + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("expiring", "31-12-2022"); + new ListStockCommand(parameters).execute(); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByDescription_expectStocksWithDescription() { + String expectedOutput = + "+====+==============+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+=============+==============+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+-------------+--------------+"; + + executeListStockCommand("d", "INFECTIONS"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStockCommand_filterByMaxQuantity1000_expectStocksWithMaxQuantity1000() { + String expectedOutput = + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + + executeListStockCommand("m", "1000"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + //@@author alvintan01 + + @Test + public void listStock_sortByIdAscending_expectStocksWithSortedIdAscending() { + String expectedOutput = + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + executeListStockCommand("sort", "i"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStock_sortByNameAscending_expectStocksWithSortedNameAscending() { + String expectedOutput = + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + executeListStockCommand("sort", "n"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStock_sortByPriceAscending_expectStocksWithSortedPriceAscending() { + String expectedOutput = + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + executeListStockCommand("sort", "p"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStock_sortByExpiryDescending_expectStocksWithSortedExpiryDescending() { + String expectedOutput = + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + executeListStockCommand("rsort", "e"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStock_sortByDescriptionDescending_expectStocksWithSortedDescriptionDescending() { + String expectedOutput = + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + executeListStockCommand("rsort", "d"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStock_sortByMaxQuantityDescending_expectStocksWithSortedMaxQuantityDescending() { + String expectedOutput = + "+====+==============+========+==============+=============+==================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+==============+========+==============+=============+==================+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 4 | SIMVASTATIN | $20.00 | 25 | 10-10-2023 | HIGH CHOLESTEROL | 800 | \n" + + "| | | | PENDING: 20 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 5 | LISINOPRIL | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "| | | | PENDING: 200 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 3 | VICODIN | $10.00 | 20 | 30-09-2022 | SEVERE PAIN | 500 | \n" + + "| | | | PENDING: 30 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+\n" + + "| 6 | AZITHROMYCIN | $20.00 | 35 | 15-10-2023 | INFECTIONS | 100 | \n" + + "| | | | PENDING: 100 | | | | \n" + + "+----+--------------+--------+--------------+-------------+------------------+--------------+"; + executeListStockCommand("rsort", "m"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void listStock_columnDoesNotExist_expectError() { + String expectedOutput = "Invalid column name/alias! Column names can only be [ID, NAME, PRICE, QUANTITY, " + + "EXPIRY_DATE, DESCRIPTION, MAX_QUANTITY] and the respective aliases are [i, n, p, q, e, d, m]."; + executeListStockCommand("sort", "a"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/command/stock/UpdateStockCommandTest.java b/src/test/java/command/stock/UpdateStockCommandTest.java new file mode 100644 index 0000000000..de3fa13c3c --- /dev/null +++ b/src/test/java/command/stock/UpdateStockCommandTest.java @@ -0,0 +1,172 @@ +package command.stock; + +import command.Command; +import command.Data; +import inventory.Medicine; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author a-tph + +class UpdateStockCommandTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + Data.generateTestData(); + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + @AfterAll + public static void clearData() { + Data.clearTestData(); + } + + + private void executeUpdateStockCommand(String id, String name) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("n", name); + Command command = new UpdateStockCommand(parameters); + command.execute(); + } + + private void executeUpdateStockCommandMaxQty(String id, String maxQty) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("m", maxQty); + Command command = new UpdateStockCommand(parameters); + command.execute(); + } + + private void executeUpdateStockCommandQty(String id, String qty) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("q", qty); + Command command = new UpdateStockCommand(parameters); + command.execute(); + } + + private void executeUpdateStockCommandQtyAndMaxQty(String id, String qty, String maxQty) { + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("i", id); + parameters.put("q", qty); + parameters.put("m", maxQty); + Command command = new UpdateStockCommand(parameters); + command.execute(); + } + + @Test + public void updateStock_validName_expectValidUpdate() { + String expectedOutput = "Updated! Number of rows affected: 1\n" + + "Stock Id changed from:\n" + + "5 -> 7\n" + + "+====+======+========+==========+=============+================+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+======+========+==========+=============+================+==============+\n" + + "| 7 | NEW | $20.00 | 25 | 15-10-2023 | HYPOTHYROIDISM | 800 | \n" + + "+----+------+--------+----------+-------------+----------------+--------------+"; + executeUpdateStockCommand("5", "new"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_validFields_expectValidUpdate() { + String expectedOutput = "Updated! Number of rows affected: 2\n" + + "Stock Id changed from:\n" + + "1 -> 7\n" + + "2 -> 8\n" + + "+====+========+========+==========+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+========+========+==========+=============+=============+==============+\n" + + "| 7 | NEWMED | $20.00 | 20 | 13-09-2022 | HEADACHES | 1000 | \n" + + "+----+--------+--------+----------+-------------+-------------+--------------+\n" + + "| 8 | NEWMED | $20.00 | 10 | 14-09-2022 | HEADACHES | 1000 | \n" + + "+----+--------+--------+----------+-------------+-------------+--------------+"; + executeUpdateStockCommand("1", "newMed"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_invalidQuantity_expectInvalidUpdate() { + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 10009, Max Quantity: 1000"; + executeUpdateStockCommandQty("1", "9999"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_validQuantity_expectValidUpdate() { + String expectedOutput = "Updated! Number of rows affected: 1\n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 10 | 13-09-2022 | HEADACHES | 1000 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + executeUpdateStockCommandQty("1", "10"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_validMaxQty_expectValidUpdate() { + String expectedOutput = "Updated! Number of rows affected: 2\n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 20 | 13-09-2022 | HEADACHES | 99999 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 99999 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + executeUpdateStockCommandMaxQty("1", "99999"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_invalidMaxQty_expectInvalidUpdate() { + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 30, Max Quantity: 1"; + executeUpdateStockCommandMaxQty("1", "1"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_InvalidQtyValidMax_expectInvalidUpdate() { + String expectedOutput = "Quantity cannot be more than maximum quantity!\n" + + "Quantity: 100009, Max Quantity: 100"; + executeUpdateStockCommandQtyAndMaxQty("1", "99999", "100"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void updateStock_validQtyAndMax_expectValidUpdate() { + String expectedOutput = "Updated! Number of rows affected: 2\n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| ID | NAME | PRICE | QUANTITY | EXPIRY_DATE | DESCRIPTION | MAX_QUANTITY | \n" + + "+====+=========+========+==============+=============+=============+==============+\n" + + "| 1 | PANADOL | $20.00 | 999 | 13-09-2022 | HEADACHES | 9999 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+\n" + + "| 2 | PANADOL | $20.00 | 10 | 14-09-2022 | HEADACHES | 9999 | \n" + + "| | | | PENDING: 150 | | | | \n" + + "+----+---------+--------+--------------+-------------+-------------+--------------+"; + executeUpdateStockCommandQtyAndMaxQty("1", "999", "9999"); + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/utilities/parser/CommandParserTest.java b/src/test/java/utilities/parser/CommandParserTest.java new file mode 100644 index 0000000000..f34883021c --- /dev/null +++ b/src/test/java/utilities/parser/CommandParserTest.java @@ -0,0 +1,121 @@ +package utilities.parser; + +import command.Command; +import command.ExitCommand; +import command.stock.AddStockCommand; +import errors.InvalidCommandException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import utilities.ui.Ui; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author alvintan01 + +public class CommandParserTest { + private final PrintStream standardOut = System.out; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + public void tearDown() { + System.setOut(standardOut); + } + + Ui ui = Ui.getInstance(); + CommandParser commandParser = new CommandParser(); + + @Test + public void processCommand_exitCommand_expectExitObject() { + try { + Command command = commandParser.processCommand("exit", "", Mode.STOCK); + assertEquals(command.getClass(), ExitCommand.class); + } catch (InvalidCommandException e) { + e.printStackTrace(); + } + } + + @Test + public void processCommand_addCommand_expectAddStockObject() { + try { + Command command = commandParser.processCommand("add", + "n/name p/10 q/20 e/10-10-2021 d/desc m/100", Mode.STOCK); + assertEquals(command.getClass(), AddStockCommand.class); + } catch (InvalidCommandException e) { + e.printStackTrace(); + } + } + + @Test + public void parseCommand_oneSeparator_expectTwoParts() { + String inputString = "listorder i/1"; + String[] stringParts = commandParser.parseCommand(inputString); + assertEquals(2, stringParts.length); + } + + + @Test + public void parseParameters_twoParameters_expectTwoParts() { + String inputString = "i/1 n/name"; + LinkedHashMap parametersValues = commandParser.parseParameters(inputString); + assertEquals(2, parametersValues.keySet().size()); + } + + @Test + public void parseParameters_threeParameters_expectThreeParts() { + String inputString = "i/1 n/name p/20"; + LinkedHashMap parametersValues = commandParser.parseParameters(inputString); + assertEquals(3, parametersValues.keySet().size()); + } + + @Test + public void changeMode_currentModeStockNewModePrescription_expectModePrescription() { + Mode mode = commandParser.changeMode(ui, "prescription", Mode.STOCK); + assertEquals(mode, Mode.PRESCRIPTION); + } + + @Test + public void changeMode_currentModeStockNewModeOrder_expectModeOrder() { + Mode mode = commandParser.changeMode(ui, "order", Mode.STOCK); + assertEquals(mode, Mode.ORDER); + } + + @Test + public void changeMode_currentModeOrderNewModeStock_expectModeStock() { + Mode mode = commandParser.changeMode(ui, "stock", Mode.ORDER); + assertEquals(mode, Mode.STOCK); + } + + @Test + public void changeMode_currentStockNewModeStock_expectModeAlreadyInStockError() { + String expectedOutput = "Already in STOCK mode!"; + Mode mode = commandParser.changeMode(ui, "stock", Mode.STOCK); + // Output stream will include \r for each line break + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void changeMode_currentModeOrderNewModeOrder_expectModeAlreadyInOrderError() { + String expectedOutput = "Already in ORDER mode!"; + Mode mode = commandParser.changeMode(ui, "order", Mode.ORDER); + // Output stream will include \r for each line break + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } + + @Test + public void changeMode_currentModePrescriptionNewModePrescription_expectModeAlreadyInPrescriptionError() { + String expectedOutput = "Already in PRESCRIPTION mode!"; + Mode mode = commandParser.changeMode(ui, "prescription", Mode.PRESCRIPTION); + // Output stream will include \r for each line break + assertEquals(expectedOutput, outputStream.toString().trim().replace("\r", "")); + } +} diff --git a/src/test/java/utilities/parser/DateParserTest.java b/src/test/java/utilities/parser/DateParserTest.java new file mode 100644 index 0000000000..379bddd1b7 --- /dev/null +++ b/src/test/java/utilities/parser/DateParserTest.java @@ -0,0 +1,47 @@ +package utilities.parser; + +import org.junit.jupiter.api.Test; + +import java.util.Date; +import java.text.ParseException; +import java.util.GregorianCalendar; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +//@@author RemusTeo + +public class DateParserTest { + + @Test + public void stringToDate_validDate_expectValid() { + try { + Date date = new GregorianCalendar(2021, 8, 13).getTime(); + Date parsedDate = DateParser.stringToDate("13-9-2021"); + assertEquals(date, parsedDate); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + @Test + public void stringToDate_invalidDate_exceptionThrown() { + assertThrows(ParseException.class, () -> DateParser.stringToDate("99-99-2021")); + } + + @Test + public void dateToString_validString_expectValid() { + Date date = new GregorianCalendar(2021, 8, 13).getTime(); + String parsedStr = DateParser.dateToString(date); + assertEquals("13-09-2021", parsedStr); + } + + @Test + public void removeTime_validDate_expectValid() { + Date date = new GregorianCalendar(2021, 8, 13, 10, 10, 10).getTime(); + Date dateWithoutTime = DateParser.removeTime(date); + Date expectedDate = new GregorianCalendar(2021, 8, 13).getTime(); + assertEquals(expectedDate, dateWithoutTime); + } + +} diff --git a/src/test/java/utilities/parser/MedicineValidatorTest.java b/src/test/java/utilities/parser/MedicineValidatorTest.java new file mode 100644 index 0000000000..e0f7768bbc --- /dev/null +++ b/src/test/java/utilities/parser/MedicineValidatorTest.java @@ -0,0 +1,40 @@ +package utilities.parser; + +import org.junit.jupiter.api.Test; +import utilities.ui.Ui; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MedicineValidatorTest { + private Ui ui = new Ui(); + StockValidator stockValidator = new StockValidator(); + + @Test + public void checkValidName_validName_expectTrue() { + String inputString = "panadol"; + boolean isValid = stockValidator.isValidName(ui, inputString); + assertTrue(isValid); + } + + @Test + public void checkValidName_validName_expectFalse() { + String inputString = ""; + boolean isInvalid = stockValidator.isValidName(ui, inputString); + assertFalse(isInvalid); + } + + @Test + public void checkValidQuantity_validQuantity_expectTrue() { + String inputString = "0"; + boolean isValid = stockValidator.isValidQuantity(ui, inputString); + assertTrue(isValid); + } + + @Test + public void checkValidQuantity_validQuantity_expectFalse() { + String inputString = "-1"; + boolean isInvalid = stockValidator.isValidQuantity(ui, inputString); + assertFalse(isInvalid); + } +} diff --git a/src/test/java/utilities/parser/OrderValidatorTest.java b/src/test/java/utilities/parser/OrderValidatorTest.java new file mode 100644 index 0000000000..3eb4d489c8 --- /dev/null +++ b/src/test/java/utilities/parser/OrderValidatorTest.java @@ -0,0 +1,95 @@ +package utilities.parser; + +import inventory.Medicine; +import inventory.Order; +import org.junit.jupiter.api.Test; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author deonchung + +public class OrderValidatorTest { + private Ui ui = new Ui(); + OrderValidator orderValidator = new OrderValidator(); + + @Test + public void checkValidOrderId_validId_expectTrue() { + ArrayList tempOrder = new ArrayList<>(); + try { + Order.setOrderCount(0); + tempOrder.add(new Order("SIMVASTATIN", 20, DateParser.stringToDate("11-10-2021"))); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + + boolean isValid = orderValidator.isValidOrderId(ui, "1", tempOrder); + + assertTrue(isValid); + } + + @Test + public void checkValidStockId_invalidId_expectFalse() { + ArrayList tempOrder = new ArrayList<>(); + try { + tempOrder.add(new Order("SIMVASTATIN", 20, DateParser.stringToDate("11-10-2021"))); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + + boolean isInvalid = orderValidator.isValidOrderId(ui, "5", tempOrder); + + assertFalse(isInvalid); + } + + @Test + public void checkValidExpiry_validDate_expectTrue() { + String inputDate = "08-10-2021"; + boolean isValid = orderValidator.isValidDate(ui, inputDate); + assertTrue(isValid); + } + + @Test + public void checkValidExpiry_invalidDate_expectFalse() { + String inputDate = "8 Oct 2021"; + boolean isInvalid = orderValidator.isValidDate(ui, inputDate); + assertFalse(isInvalid); + } + + @Test + public void checkValidColumn_validColumn_expectTrue() { + String inputColumnName = "NAME"; + boolean isValid = orderValidator.isValidColumn(ui, inputColumnName); + assertTrue(isValid); + + } + + @Test + public void checkValidColumn_invalidColumn_expectFalse() { + String inputColumnName = "panadol"; + boolean isInvalid = orderValidator.isValidColumn(ui, inputColumnName); + assertFalse(isInvalid); + + } + + @Test + public void checkValidStatus_validStatus_expectTrue() { + String inputStatusName = "PENDING"; + boolean isValid = orderValidator.isValidStatus(ui, inputStatusName); + assertTrue(isValid); + + } + + @Test + public void checkValidStatus_invalidStatus_expectFalse() { + String inputStatusName = "panadol"; + boolean isInvalid = orderValidator.isValidStatus(ui, inputStatusName); + assertFalse(isInvalid); + + } + +} diff --git a/src/test/java/utilities/parser/PrescriptionValidatorTest.java b/src/test/java/utilities/parser/PrescriptionValidatorTest.java new file mode 100644 index 0000000000..c9568da070 --- /dev/null +++ b/src/test/java/utilities/parser/PrescriptionValidatorTest.java @@ -0,0 +1,43 @@ +package utilities.parser; + +import org.junit.jupiter.api.Test; +import utilities.ui.Ui; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +//@@author deonchung + +public class PrescriptionValidatorTest { + private Ui ui = new Ui(); + PrescriptionValidator prescriptionValidator = new PrescriptionValidator(); + + @Test + public void checkValidCustomerId_validCustomerId_expectTrue() { + String inputString = "123"; + boolean isValid = prescriptionValidator.isValidCustomerId(ui, inputString); + assertTrue(isValid); + } + + @Test + public void checkValidCustomerId_emptyInput_expectFalse() { + String inputString = ""; + boolean isValid = prescriptionValidator.isValidCustomerId(ui, inputString); + assertFalse(isValid); + } + + @Test + public void checkValidStaffName_validStaffName_expectTrue() { + String inputString = "Mary"; + boolean isValid = prescriptionValidator.isValidCustomerId(ui, inputString); + assertTrue(isValid); + } + + @Test + public void checkValidStaffName_emptyInput_expectFalse() { + String inputString = ""; + boolean isValid = prescriptionValidator.isValidCustomerId(ui, inputString); + assertFalse(isValid); + } + +} diff --git a/src/test/java/utilities/parser/StockManagerTest.java b/src/test/java/utilities/parser/StockManagerTest.java new file mode 100644 index 0000000000..a76bc502fb --- /dev/null +++ b/src/test/java/utilities/parser/StockManagerTest.java @@ -0,0 +1,85 @@ +package utilities.parser; + +import inventory.Medicine; +import inventory.Stock; +import org.junit.jupiter.api.Test; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +public class StockManagerTest { + + @Test + void getTotalStockQuantity_validStock_expectCorrectQuantity() throws ParseException { + // Add dummy medicine values for testing + ArrayList medicines = new ArrayList<>(); + medicines.add(new Stock("PANADOL", 10, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + medicines.add(new Stock("PANADOL", 30, 20, DateParser.stringToDate("14-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + medicines.add(new Stock("AZITHROMYCIN", 20, 20, DateParser.stringToDate("14-9-2021"), + "USED FOR TREATING EAR, THROAT, AND SINUS INFECTIONS", 2000)); + + int panadolStockQuantity = StockManager.getTotalStockQuantity(medicines, "PANADOL"); + int azithromycinStockQuantity = StockManager.getTotalStockQuantity(medicines, "AZITHROMYCIN"); + assertEquals(40, panadolStockQuantity); + assertEquals(20, azithromycinStockQuantity); + } + + @Test + void getTotalStockQuantity_emptyStock_expectNoQuantity() { + ArrayList medicines = new ArrayList<>(); + int totalStockQuantity = StockManager.getTotalStockQuantity(medicines, "PANADOL"); + assertEquals(0, totalStockQuantity); + } + + @Test + void getMaxStockQuantity_validStock_expectCorrectMaxQuantity() throws ParseException { + // Add dummy medicine values for testing + ArrayList medicines = new ArrayList<>(); + medicines.add(new Stock("PANADOL", 10, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + medicines.add(new Stock("PANADOL", 30, 20, DateParser.stringToDate("14-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + medicines.add(new Stock("AZITHROMYCIN", 20, 20, DateParser.stringToDate("14-9-2021"), + "USED FOR TREATING EAR, THROAT, AND SINUS INFECTIONS", 2000)); + + int panadolMaxStockQuantity = StockManager.getMaxStockQuantity(medicines, "PANADOL"); + int azithromycinMaxStockQuantity = StockManager.getMaxStockQuantity(medicines, "AZITHROMYCIN"); + assertEquals(1000, panadolMaxStockQuantity); + assertEquals(2000, azithromycinMaxStockQuantity); + } + + @Test + void getMaxStockQuantity_emptyStock_assertionError() { + ArrayList medicines = new ArrayList<>(); + assertThrows(AssertionError.class, () -> StockManager.getMaxStockQuantity(medicines, "PANADOL")); + } + + @Test + void extractStockObject_noStock_assertionError() { + ArrayList medicines = new ArrayList<>(); + HashMap parameters = new HashMap<>(); + parameters.put("i", "1"); + assertThrows(AssertionError.class, () -> StockManager.extractStockObject(parameters, medicines)); + } + + @Test + void extractStockObject_IdNotFound_assertionError() throws ParseException { + ArrayList medicines = new ArrayList<>(); + medicines.add(new Stock("PANADOL", 10, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + medicines.add(new Stock("PANADOL", 30, 20, DateParser.stringToDate("14-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + HashMap parameters = new HashMap<>(); + parameters.put("i", "3"); + assertThrows(AssertionError.class, () -> StockManager.extractStockObject(parameters, medicines)); + } + + +} \ No newline at end of file diff --git a/src/test/java/utilities/parser/StockValidatorTest.java b/src/test/java/utilities/parser/StockValidatorTest.java new file mode 100644 index 0000000000..114e6e5ede --- /dev/null +++ b/src/test/java/utilities/parser/StockValidatorTest.java @@ -0,0 +1,168 @@ +package utilities.parser; + +import inventory.Medicine; +import inventory.Stock; +import org.junit.jupiter.api.Test; +import utilities.ui.Ui; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StockValidatorTest { + private Ui ui = new Ui(); + StockValidator stockValidator = new StockValidator(); + + @Test + public void checkValidStockId_validId_expectTrue() { + ArrayList tempMedicines = new ArrayList<>(); + try { + Stock.setStockCount(0); + tempMedicines.add(new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + boolean isValid = stockValidator.isValidStockId(ui, "1", tempMedicines); + assertTrue(isValid); + } + + @Test + public void checkValidStockId_validId_expectFalse() { + ArrayList tempMedicines = new ArrayList<>(); + try { + tempMedicines.add(new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + boolean isInvalid = stockValidator.isValidStockId(ui, "5", tempMedicines); + assertFalse(isInvalid); + } + + @Test + public void checkValidPrice_validPrice_expectTrue() { + String inputPrice = "20"; + boolean isValid = stockValidator.isValidPrice(ui, inputPrice); + assertTrue(isValid); + } + + @Test + public void checkValidPrice_validPrice_expectFalse() { + String inputPrice = "-1"; + boolean isInvalid = stockValidator.isValidPrice(ui, inputPrice); + assertFalse(isInvalid); + } + + @Test + public void checkValidExpiry_validDate_expectTrue() { + String inputDate = "08-10-2021"; + boolean isValid = stockValidator.isValidExpiry(ui, inputDate); + assertTrue(isValid); + } + + @Test + public void checkValidExpiry_validDate_expectFalse() { + String inputDate = "8 Oct 2021"; + boolean isInvalid = stockValidator.isValidExpiry(ui, inputDate); + assertFalse(isInvalid); + } + + @Test + public void checkValidDescription_validDescription_expectTrue() { + String inputDescription = "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS"; + boolean isValid = stockValidator.isValidDescription(ui, inputDescription); + assertTrue(isValid); + } + + @Test + public void checkValidDescription_validDescription_expectFalse() { + String inputDescription = ""; + boolean isInvalid = stockValidator.isValidDescription(ui, inputDescription); + assertFalse(isInvalid); + } + + @Test + public void checkValidMax_validQuantity_expectTrue() { + String inputMaxQuantity = "100"; + boolean isValid = stockValidator.isValidMaxQuantity(ui, inputMaxQuantity); + assertTrue(isValid); + } + + @Test + public void checkValidMax_validQuantity_expectFalse() { + String inputMaxQuantity = ""; + boolean isInvalid = stockValidator.isValidMaxQuantity(ui, inputMaxQuantity); + assertFalse(isInvalid); + } + + @Test + public void checkValidColumn_validColumn_expectTrue() { + String inputColumnName = "NAME"; + boolean isValid = stockValidator.isValidColumn(ui, inputColumnName); + assertTrue(isValid); + } + + @Test + public void checkValidColumn_validColumn_expectFalse() { + String inputColumnName = "panadol"; + boolean isInvalid = stockValidator.isValidColumn(ui, inputColumnName); + assertFalse(isInvalid); + } + + @Test + public void checkValidity_checkQuantity_expectTrue() { + int quantity = 10; + int maxQuantity = 100; + boolean isValid = stockValidator.quantityValidityChecker(ui, quantity, maxQuantity); + assertTrue(isValid); + } + + @Test + public void checkValidity_checkQuantity_expectFalse() { + int quantity = 10; + int maxQuantity = 1; + boolean isInvalid = stockValidator.quantityValidityChecker(ui, quantity, maxQuantity); + assertFalse(isInvalid); + } + + @Test + public void checkValidity_checkDate_expectTrue() { + ArrayList tempMedicines = new ArrayList<>(); + try { + tempMedicines.add(new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + + final SimpleDateFormat DateFor = new SimpleDateFormat("dd/MM/yyyy"); + final Date expiryDate = DateFor.parse("10-10-2021"); + String inputName = "panadol"; + + boolean isValid = stockValidator.dateValidityChecker(ui, tempMedicines, expiryDate, inputName); + assertTrue(isValid); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + } + + @Test + public void checkValidity_checkDate_expectFalse() { + ArrayList tempMedicines = new ArrayList<>(); + try { + tempMedicines.add(new Stock("PANADOL", 20, 20, DateParser.stringToDate("13-9-2021"), + "BEST MEDICINE TO CURE HEADACHES, FEVER AND PAINS", 1000)); + + final SimpleDateFormat DateFor = new SimpleDateFormat("dd/MM/yyyy"); + final Date expiryDate = DateFor.parse("13-9-2021"); + String inputName = "panadol"; + + boolean isInvalid = stockValidator.dateValidityChecker(ui, tempMedicines, expiryDate, inputName); + assertFalse(isInvalid); + } catch (ParseException e) { + ui.print("Unable to parse date!"); + } + } +}